From 26fcaf6e75fc8fbc2fc95361c6f5fba6e16216a1 Mon Sep 17 00:00:00 2001 From: Dominic Masters Date: Thu, 25 Jun 2026 11:53:15 -0500 Subject: [PATCH 01/16] Restored some stuff --- archive/rpg/rpg.c | 4 +- src/dusk/display/screen/screen.c | 32 +++++++++++++++ src/dusk/display/screen/screen.h | 8 ++++ src/dusk/rpg/entity/CMakeLists.txt | 3 +- src/dusk/rpg/entity/entity.c | 23 +++++++++++ src/dusk/rpg/entity/entity.h | 41 +++++++++++++++++-- src/dusk/rpg/entity/entityanim.c | 32 ++++++++++++--- src/dusk/rpg/entity/entityanim.h | 2 + src/dusk/rpg/entity/entityinteract.c | 40 ++++++++++++++++++ src/dusk/rpg/entity/entityinteract.h | 49 +++++++++++++++++++++++ src/dusk/rpg/entity/player.c | 11 ++--- src/dusk/rpg/overworld/worldpos.h | 9 ----- src/dusk/rpg/rpg.c | 8 ++++ src/dusk/rpg/rpgcamera.c | 21 +++++++--- src/dusk/rpg/rpgcamera.h | 10 ++--- src/dusk/scene/overworld/sceneoverworld.c | 18 ++------- src/dusk/scene/scene.c | 4 +- src/dusk/ui/uifps.c | 7 ++-- src/dusk/ui/uiloading.c | 6 ++- src/dusk/ui/uitextbox.c | 7 ++-- 20 files changed, 273 insertions(+), 62 deletions(-) create mode 100644 src/dusk/rpg/entity/entityinteract.c create mode 100644 src/dusk/rpg/entity/entityinteract.h diff --git a/archive/rpg/rpg.c b/archive/rpg/rpg.c index ead5c870..653b36fe 100644 --- a/archive/rpg/rpg.c +++ b/archive/rpg/rpg.c @@ -41,9 +41,7 @@ errorret_t rpgInit(void) { errorret_t rpgUpdate(void) { #if TIME_FIXED == 0 - if(TIME.dynamicUpdate) { - errorOk(); - } + if(TIME.dynamicUpdate) errorOk(); #endif // TODO: Do not update if the scene is not the map scene? diff --git a/src/dusk/display/screen/screen.c b/src/dusk/display/screen/screen.c index 3f5ee708..9e2e7d10 100644 --- a/src/dusk/display/screen/screen.c +++ b/src/dusk/display/screen/screen.c @@ -53,6 +53,10 @@ errorret_t screenBind() { SCREEN.width = frameBufferGetWidth(FRAMEBUFFER_BOUND); SCREEN.height = frameBufferGetHeight(FRAMEBUFFER_BOUND); SCREEN.aspect = frameBufferGetAspect(FRAMEBUFFER_BOUND); + SCREEN.scanX = 0; + SCREEN.scanY = 0; + SCREEN.scanWidth = SCREEN.width; + SCREEN.scanHeight = SCREEN.height; // No needd for a framebuffer. #ifdef DUSK_DISPLAY_SIZE_DYNAMIC @@ -69,6 +73,10 @@ errorret_t screenBind() { SCREEN.width = SCREEN.fixedSize.width; SCREEN.height = SCREEN.fixedSize.height; SCREEN.aspect = (float_t)SCREEN.width / (float_t)SCREEN.height; + SCREEN.scanX = 0; + SCREEN.scanY = 0; + SCREEN.scanWidth = SCREEN.width; + SCREEN.scanHeight = SCREEN.height; if(SCREEN.framebufferReady) { // Is current framebuffer the correct size? @@ -106,6 +114,10 @@ errorret_t screenBind() { SCREEN.width = fbWidth; SCREEN.height = fbHeight; SCREEN.aspect = (float_t)SCREEN.width / (float_t)SCREEN.height; + SCREEN.scanX = 0; + SCREEN.scanY = 0; + SCREEN.scanWidth = SCREEN.width; + SCREEN.scanHeight = SCREEN.height; if(SCREEN.framebufferReady) { errorChain(frameBufferDispose(&SCREEN.framebuffer)); @@ -136,6 +148,10 @@ errorret_t screenBind() { SCREEN.width = newFbWidth; SCREEN.height = newFbHeight; SCREEN.aspect = curFbAspect; + SCREEN.scanX = 0; + SCREEN.scanY = 0; + SCREEN.scanWidth = SCREEN.width; + SCREEN.scanHeight = SCREEN.height; errorChain(frameBufferBind(&SCREEN.framebuffer)); errorOk(); } @@ -152,6 +168,10 @@ errorret_t screenBind() { SCREEN.width = newFbWidth; SCREEN.height = newFbHeight; SCREEN.aspect = (float_t)SCREEN.width / (float_t)SCREEN.height; + SCREEN.scanX = 0; + SCREEN.scanY = 0; + SCREEN.scanWidth = SCREEN.width; + SCREEN.scanHeight = SCREEN.height; SCREEN.framebufferReady = true; // Bind FB @@ -171,6 +191,10 @@ errorret_t screenBind() { SCREEN.width = newFbWidth; SCREEN.height = newFbHeight; SCREEN.aspect = (float_t)SCREEN.width / (float_t)SCREEN.height; + SCREEN.scanX = 0; + SCREEN.scanY = 0; + SCREEN.scanWidth = SCREEN.width; + SCREEN.scanHeight = SCREEN.height; if(fbWidth == newFbWidth && fbHeight == newFbHeight) { // No need to use framebuffer. @@ -217,6 +241,10 @@ errorret_t screenBind() { SCREEN.width = newFbWidth; SCREEN.height = newFbHeight; SCREEN.aspect = (float_t)SCREEN.width / (float_t)SCREEN.height; + SCREEN.scanX = 0; + SCREEN.scanY = 0; + SCREEN.scanWidth = SCREEN.width; + SCREEN.scanHeight = SCREEN.height; if(fbWidth == newFbWidth && fbHeight == newFbHeight) { // No need to use framebuffer. @@ -258,6 +286,10 @@ errorret_t screenBind() { float_t fbAspect = fbWidth / fbHeight; SCREEN.width = (int32_t)floorf(SCREEN.height * fbAspect); SCREEN.aspect = (float_t)SCREEN.width / (float_t)SCREEN.height; + SCREEN.scanX = 0; + SCREEN.scanY = 0; + SCREEN.scanWidth = SCREEN.width; + SCREEN.scanHeight = SCREEN.height; break; } diff --git a/src/dusk/display/screen/screen.h b/src/dusk/display/screen/screen.h index de69e7af..bf3a26a6 100644 --- a/src/dusk/display/screen/screen.h +++ b/src/dusk/display/screen/screen.h @@ -44,6 +44,14 @@ typedef struct { int32_t width; int32_t height; float_t aspect; + + // Overscan-safe area; defaults to full viewport each frame. + // Platforms with TV overscan set these after screenBind(). + int32_t scanX; + int32_t scanY; + int32_t scanWidth; + int32_t scanHeight; + color_t background; #ifdef DUSK_DISPLAY_SIZE_DYNAMIC diff --git a/src/dusk/rpg/entity/CMakeLists.txt b/src/dusk/rpg/entity/CMakeLists.txt index f6e4cda5..a254c108 100644 --- a/src/dusk/rpg/entity/CMakeLists.txt +++ b/src/dusk/rpg/entity/CMakeLists.txt @@ -8,7 +8,8 @@ target_sources(${DUSK_LIBRARY_TARGET_NAME} PUBLIC entity.c entityanim.c + entitydir.c + entityinteract.c npc.c player.c - entitydir.c ) \ No newline at end of file diff --git a/src/dusk/rpg/entity/entity.c b/src/dusk/rpg/entity/entity.c index 98052149..274ac576 100644 --- a/src/dusk/rpg/entity/entity.c +++ b/src/dusk/rpg/entity/entity.c @@ -49,13 +49,27 @@ void entityUpdate(entity_t *entity) { } } +bool_t entityCanTurn(entity_t *entity) { + return entity->animation == ENTITY_ANIM_IDLE; +} + +bool_t entityCanWalk(entity_t *entity) { + return entity->animation == ENTITY_ANIM_IDLE; +} + +bool_t entityCanRun(entity_t *entity) { + return entity->animation == ENTITY_ANIM_IDLE; +} + void entityTurn(entity_t *entity, const entitydir_t direction) { + if(!entityCanTurn(entity)) return; entity->direction = direction; entity->animation = ENTITY_ANIM_TURN; entity->animTime = ENTITY_ANIM_TURN_DURATION; } void entityWalk(entity_t *entity, const entitydir_t direction) { + if(!entityCanWalk(entity)) return; // TODO: Animation, delay, etc. entity->direction = direction; @@ -172,6 +186,15 @@ void entityWalk(entity_t *entity, const entitydir_t direction) { } } +void entityRun(entity_t *entity, const entitydir_t direction) { + if(!entityCanRun(entity)) return; + entityWalk(entity, direction); + if(entity->animation == ENTITY_ANIM_WALK) { + entity->animation = ENTITY_ANIM_RUN; + entity->animTime = ENTITY_ANIM_RUN_DURATION; + } +} + entity_t * entityGetAt(const worldpos_t position) { entity_t *ent = ENTITIES; do { diff --git a/src/dusk/rpg/entity/entity.h b/src/dusk/rpg/entity/entity.h index e6a93b5c..6d6a566a 100644 --- a/src/dusk/rpg/entity/entity.h +++ b/src/dusk/rpg/entity/entity.h @@ -8,6 +8,7 @@ #pragma once #include "entitydir.h" #include "entityanim.h" +#include "entityinteract.h" #include "entitytype.h" #include "npc.h" @@ -21,11 +22,13 @@ typedef struct entity_s { // Movement entitydir_t direction; worldpos_t position; - tilepos_t subPosition; worldpos_t lastPosition; + vec3 renderPosition; entityanim_t animation; float_t animTime; + + entityinteract_t interact; } entity_t; extern entity_t ENTITIES[ENTITY_COUNT]; @@ -45,9 +48,33 @@ void entityInit(entity_t *entity, const entitytype_t type); */ void entityUpdate(entity_t *entity); +/** + * Returns true if the entity is in a state where it can turn. + * + * @param entity Pointer to the entity to check. + * @returns True if the entity can turn. + */ +bool_t entityCanTurn(entity_t *entity); + +/** + * Returns true if the entity is in a state where it can walk. + * + * @param entity Pointer to the entity to check. + * @returns True if the entity can walk. + */ +bool_t entityCanWalk(entity_t *entity); + +/** + * Returns true if the entity is in a state where it can run. + * + * @param entity Pointer to the entity to check. + * @returns True if the entity can run. + */ +bool_t entityCanRun(entity_t *entity); + /** * Turn an entity to face a new direction. - * + * * @param entity Pointer to the entity to turn. * @param direction The direction to face. */ @@ -55,12 +82,20 @@ void entityTurn(entity_t *entity, const entitydir_t direction); /** * Make an entity walk in a direction. - * + * * @param entity Pointer to the entity to make walk. * @param direction The direction to walk in. */ void entityWalk(entity_t *entity, const entitydir_t direction); +/** + * Make an entity run in a direction. + * + * @param entity Pointer to the entity to make run. + * @param direction The direction to run in. + */ +void entityRun(entity_t *entity, const entitydir_t direction); + /** * Gets the entity at a specific world position. * diff --git a/src/dusk/rpg/entity/entityanim.c b/src/dusk/rpg/entity/entityanim.c index d5024815..8882a222 100644 --- a/src/dusk/rpg/entity/entityanim.c +++ b/src/dusk/rpg/entity/entityanim.c @@ -9,11 +9,33 @@ #include "time/time.h" void entityAnimUpdate(entity_t *entity) { - if(entity->animation == ENTITY_ANIM_IDLE) return; + if(entity->animation != ENTITY_ANIM_IDLE) { + entity->animTime -= TIME.delta; + if(entity->animTime <= 0) { + entity->animation = ENTITY_ANIM_IDLE; + entity->animTime = 0; + } + } - entity->animTime -= TIME.delta; - if(entity->animTime <= 0) { - entity->animation = ENTITY_ANIM_IDLE; - entity->animTime = 0; + if( + entity->animation == ENTITY_ANIM_WALK || + entity->animation == ENTITY_ANIM_RUN + ) { + float_t duration = entity->animation == ENTITY_ANIM_WALK ? + ENTITY_ANIM_WALK_DURATION : ENTITY_ANIM_RUN_DURATION; + float_t t = 1.0f - (entity->animTime / duration); + entity->renderPosition[0] = (float_t)entity->lastPosition.x + t * ( + (float_t)entity->position.x - (float_t)entity->lastPosition.x + ); + entity->renderPosition[1] = (float_t)entity->lastPosition.y + t * ( + (float_t)entity->position.y - (float_t)entity->lastPosition.y + ); + entity->renderPosition[2] = (float_t)entity->lastPosition.z + t * ( + (float_t)entity->position.z - (float_t)entity->lastPosition.z + ); + } else { + entity->renderPosition[0] = (float_t)entity->position.x; + entity->renderPosition[1] = (float_t)entity->position.y; + entity->renderPosition[2] = (float_t)entity->position.z; } } \ No newline at end of file diff --git a/src/dusk/rpg/entity/entityanim.h b/src/dusk/rpg/entity/entityanim.h index 9fb8d9ea..ccd425e6 100644 --- a/src/dusk/rpg/entity/entityanim.h +++ b/src/dusk/rpg/entity/entityanim.h @@ -10,6 +10,7 @@ #define ENTITY_ANIM_TURN_DURATION 0.06f #define ENTITY_ANIM_WALK_DURATION 0.1f +#define ENTITY_ANIM_RUN_DURATION 0.05f typedef struct entity_s entity_t; @@ -17,6 +18,7 @@ typedef enum { ENTITY_ANIM_IDLE, ENTITY_ANIM_TURN, ENTITY_ANIM_WALK, + ENTITY_ANIM_RUN, } entityanim_t; /** diff --git a/src/dusk/rpg/entity/entityinteract.c b/src/dusk/rpg/entity/entityinteract.c new file mode 100644 index 00000000..d3b3ca7f --- /dev/null +++ b/src/dusk/rpg/entity/entityinteract.c @@ -0,0 +1,40 @@ +/** + * Copyright (c) 2026 Dominic Masters + * + * This software is released under the MIT License. + * https://opensource.org/licenses/MIT + */ + +#include "entity.h" +#include "assert/assert.h" +#include "console/console.h" +#include "rpg/cutscene/cutscenesystem.h" + +void entityInteractWith(entity_t *player, entity_t *target) { + assertNotNull(player, "Player entity pointer cannot be NULL"); + assertNotNull(target, "Target entity pointer cannot be NULL"); + + switch(target->interact.type) { + case ENTITY_INTERACT_CUTSCENE: + assertNotNull( + target->interact.data.cutscene, + "Interact cutscene pointer cannot be NULL" + ); + cutsceneSystemStartCutscene(target->interact.data.cutscene); + return; + + case ENTITY_INTERACT_PRINT: + consolePrint(target->interact.data.message); + return; + + case ENTITY_INTERACT_NULL: + break; + + default: + assertUnreachable("Unknown entity interact type"); + break; + } + + if(ENTITY_CALLBACKS[target->type].interact == NULL) return; + ENTITY_CALLBACKS[target->type].interact(player, target); +} diff --git a/src/dusk/rpg/entity/entityinteract.h b/src/dusk/rpg/entity/entityinteract.h new file mode 100644 index 00000000..27a1efd6 --- /dev/null +++ b/src/dusk/rpg/entity/entityinteract.h @@ -0,0 +1,49 @@ +/** + * Copyright (c) 2026 Dominic Masters + * + * This software is released under the MIT License. + * https://opensource.org/licenses/MIT + */ + +#pragma once +#include "dusk.h" +#include "rpg/cutscene/cutscene.h" + +typedef struct entity_s entity_t; + +/** + * Describes the type of interaction an entity supports. + */ +typedef enum { + ENTITY_INTERACT_NULL = 0, + ENTITY_INTERACT_CUTSCENE, + ENTITY_INTERACT_PRINT, + ENTITY_INTERACT_COUNT +} entityinteracttype_t; + +/** + * Per-type data for an entity's interact component. + */ +typedef union { + const cutscene_t *cutscene; + char_t message[32]; +} entityinteractdata_t; + +/** + * Interact component attached to any entity that can be interacted with. + * Set type to ENTITY_INTERACT_NULL to mark the entity as non-interactable. + */ +typedef struct { + entityinteracttype_t type; + entityinteractdata_t data; +} entityinteract_t; + +/** + * Attempts to interact with the target entity on behalf of the player. + * Dispatches via the interact component; falls back to the entity type + * callback if no component is set. + * + * @param player Pointer to the player entity. + * @param target Pointer to the entity to interact with. + */ +void entityInteractWith(entity_t *player, entity_t *target); diff --git a/src/dusk/rpg/entity/player.c b/src/dusk/rpg/entity/player.c index cf33d562..b8f6bead 100644 --- a/src/dusk/rpg/entity/player.c +++ b/src/dusk/rpg/entity/player.c @@ -26,11 +26,13 @@ void playerInput(entity_t *entity) { return entityTurn(entity, dirMap->direction); } while((++dirMap)->action != 0xFF); - // Walk + // Walk / Run + bool_t running = inputIsDown(INPUT_ACTION_CANCEL); dirMap = PLAYER_INPUT_DIR_MAP; do { if(!inputIsDown(dirMap->action)) continue; if(entity->direction != dirMap->direction) continue; + if(running) return entityRun(entity, dirMap->direction); return entityWalk(entity, dirMap->direction); } while((++dirMap)->action != 0xFF); @@ -45,10 +47,9 @@ void playerInput(entity_t *entity) { z = entity->position.z; } - entity_t *interact = entityGetAt((worldpos_t){ x, y, z }); - if(interact != NULL && ENTITY_CALLBACKS[interact->type].interact != NULL) { - if(ENTITY_CALLBACKS[interact->type].interact(entity, interact)) return; - } + entity_t *target = entityGetAt((worldpos_t){ x, y, z }); + if(target == NULL) return; + entityInteractWith(entity, target); } } \ No newline at end of file diff --git a/src/dusk/rpg/overworld/worldpos.h b/src/dusk/rpg/overworld/worldpos.h index 87559cf1..8dae69f4 100644 --- a/src/dusk/rpg/overworld/worldpos.h +++ b/src/dusk/rpg/overworld/worldpos.h @@ -8,9 +8,6 @@ #pragma once #include "dusk.h" -#define TILE_POS_MIN -128 -#define TILE_POS_MAX 127 - #define CHUNK_WIDTH 16 #define CHUNK_HEIGHT CHUNK_WIDTH #define CHUNK_DEPTH 8 @@ -32,8 +29,6 @@ typedef uint32_t chunktileindex_t; typedef int32_t worldunits_t; typedef int32_t chunkunits_t; -typedef int8_t tileunit_t; - typedef struct worldpos_s { worldunit_t x, y, z; } worldpos_t; @@ -42,10 +37,6 @@ typedef struct chunkpos_t { chunkunit_t x, y, z; } chunkpos_t; -typedef struct tilepos_s { - tileunit_t x, y, z; -} tilepos_t; - /** * Compares two world positions for equality. * diff --git a/src/dusk/rpg/rpg.c b/src/dusk/rpg/rpg.c index 776f07fd..e4f95b00 100644 --- a/src/dusk/rpg/rpg.c +++ b/src/dusk/rpg/rpg.c @@ -13,6 +13,7 @@ #include "rpgcamera.h" #include "rpgtextbox.h" #include "util/memory.h" +#include "util/string.h" #include "assert/assert.h" errorret_t rpgInit(void) { @@ -37,6 +38,13 @@ errorret_t rpgInit(void) { RPG_CAMERA.mode = RPG_CAMERA_MODE_FOLLOW_ENTITY; RPG_CAMERA.followEntity.followEntityId = ent->id; + uint8_t npcIndex = entityGetAvailable(); + entity_t *npc = &ENTITIES[npcIndex]; + entityInit(npc, ENTITY_TYPE_NPC); + npc->position = (worldpos_t){ 3, 3, 0 }; + npc->interact.type = ENTITY_INTERACT_PRINT; + stringCopy(npc->interact.data.message, "hello world", 32); + // All Good! errorOk(); } diff --git a/src/dusk/rpg/rpgcamera.c b/src/dusk/rpg/rpgcamera.c index 0ab0c39e..89058643 100644 --- a/src/dusk/rpg/rpgcamera.c +++ b/src/dusk/rpg/rpgcamera.c @@ -19,17 +19,20 @@ void rpgCameraInit(void) { memoryZero(&RPG_CAMERA, sizeof(rpgcamera_t)); } -worldpos_t rpgCameraGetPosition(void) { +void rpgCameraGetPosition(vec3 out) { switch(RPG_CAMERA.mode) { case RPG_CAMERA_MODE_FREE: - return RPG_CAMERA.free; + glm_vec3_copy(RPG_CAMERA.free, out); + return; case RPG_CAMERA_MODE_FOLLOW_ENTITY: { entity_t *entity = &ENTITIES[RPG_CAMERA.followEntity.followEntityId]; if(entity->type == ENTITY_TYPE_NULL) { - return (worldpos_t){ 0, 0, 0 }; + glm_vec3_zero(out); + return; } - return entity->position; + glm_vec3_copy(entity->renderPosition, out); + return; } default: @@ -39,9 +42,15 @@ worldpos_t rpgCameraGetPosition(void) { errorret_t rpgCameraUpdate(void) { if(!mapIsLoaded()) errorOk(); - + + vec3 pos; + rpgCameraGetPosition(pos); + worldpos_t worldPos = { + (worldunit_t)pos[0], + (worldunit_t)pos[1], + (worldunit_t)pos[2] + }; chunkpos_t chunkPos; - worldpos_t worldPos = rpgCameraGetPosition(); worldPosToChunkPos(&worldPos, &chunkPos); errorChain(mapPositionSet((chunkpos_t){ diff --git a/src/dusk/rpg/rpgcamera.h b/src/dusk/rpg/rpgcamera.h index 824ca8c7..15b234a3 100644 --- a/src/dusk/rpg/rpgcamera.h +++ b/src/dusk/rpg/rpgcamera.h @@ -18,8 +18,8 @@ typedef struct { rpgcameramode_t mode; union { - worldpos_t free; - + vec3 free; + struct { uint8_t followEntityId; } followEntity; @@ -37,10 +37,10 @@ void rpgCameraInit(void); /** * Gets the RPG camera's position. - * - * @return The RPG camera's position. + * + * @param out Output vec3 filled with the camera world position. */ -worldpos_t rpgCameraGetPosition(void); +void rpgCameraGetPosition(vec3 out); /** * Updates the RPG camera. diff --git a/src/dusk/scene/overworld/sceneoverworld.c b/src/dusk/scene/overworld/sceneoverworld.c index 6ec815c9..feca011c 100644 --- a/src/dusk/scene/overworld/sceneoverworld.c +++ b/src/dusk/scene/overworld/sceneoverworld.c @@ -61,14 +61,10 @@ errorret_t sceneOverworldRender(scenedata_t *sceneData) { float_t pixelsPerUnit = 16.0f; float_t worldH = (float)SCREEN.height / pixelsPerUnit; float_t z = (worldH * 0.5f) / tanf(fov * 0.5f); - worldpos_t worldPos = rpgCameraGetPosition(); + vec3 worldPosVec; + rpgCameraGetPosition(worldPosVec); float_t offset = -16.0f; - vec3 worldPosVec = { - worldPos.x, - worldPos.y, - worldPos.z - }; glm_vec3_add(worldPosVec, (vec3){ 0.5f, 0.5f, 0.5f }, worldPosVec); glm_lookat( @@ -119,14 +115,8 @@ errorret_t sceneOverworldRender(scenedata_t *sceneData) { entity_t *ent = &ENTITIES[i]; if(ent->type == ENTITY_TYPE_NULL) continue; - vec3 position = { - ent->position.x + ((float_t)ent->subPosition.x / TILE_POS_MAX), - ent->position.y + ((float_t)ent->subPosition.y / TILE_POS_MAX), - ent->position.z + ((float_t)ent->subPosition.z / TILE_POS_MAX) + 0.01f - }; - - glm_vec3_copy(position, sprites[spriteCount].min); - glm_vec3_copy(position, sprites[spriteCount].max); + glm_vec3_copy(ent->renderPosition, sprites[spriteCount].min); + glm_vec3_copy(ent->renderPosition, sprites[spriteCount].max); glm_vec3_add( sprites[spriteCount].max, (vec3){ 1, 1, 0 }, diff --git a/src/dusk/scene/scene.c b/src/dusk/scene/scene.c index 7e530daa..6d934f09 100644 --- a/src/dusk/scene/scene.c +++ b/src/dusk/scene/scene.c @@ -44,9 +44,7 @@ errorret_t sceneUpdate(void) { } #if DUSK_TIME_DYNAMIC - if(TIME.dynamicUpdate) { - errorOk(); - } + if(TIME.dynamicUpdate) errorOk(); #endif if( diff --git a/src/dusk/ui/uifps.c b/src/dusk/ui/uifps.c index 0edf851c..901e3b09 100644 --- a/src/dusk/ui/uifps.c +++ b/src/dusk/ui/uifps.c @@ -51,7 +51,8 @@ errorret_t uiFPSDraw() { } errorChain(textDraw( - 0, 0, + (float_t)SCREEN.scanX, + (float_t)SCREEN.scanY, fpsText, textColor, &FONT_DEFAULT )); @@ -60,8 +61,8 @@ errorret_t uiFPSDraw() { int32_t versionWidth, versionHeight; textMeasure(ENGINE.version, &FONT_DEFAULT, &versionWidth, &versionHeight); errorChain(textDraw( - (float_t)(SCREEN.width - versionWidth), - (float_t)(SCREEN.height - versionHeight), + (float_t)(SCREEN.scanX + SCREEN.scanWidth - versionWidth), + (float_t)(SCREEN.scanY + SCREEN.scanHeight - versionHeight), ENGINE.version, color(255, 255, 255, 128), &FONT_DEFAULT diff --git a/src/dusk/ui/uiloading.c b/src/dusk/ui/uiloading.c index ecf0b768..89b04309 100644 --- a/src/dusk/ui/uiloading.c +++ b/src/dusk/ui/uiloading.c @@ -52,8 +52,10 @@ errorret_t uiLoadingDraw(void) { int32_t textW, textH; textMeasure(UI_LOADING_TEXT, &FONT_DEFAULT, &textW, &textH); - float_t x = (float_t)SCREEN.width - (float_t)textW - UI_LOADING_MARGIN; - float_t y = (float_t)SCREEN.height - (float_t)textH - UI_LOADING_MARGIN; + float_t x = (float_t)(SCREEN.scanX + SCREEN.scanWidth) - + (float_t)textW - UI_LOADING_MARGIN; + float_t y = (float_t)(SCREEN.scanY + SCREEN.scanHeight) - + (float_t)textH - UI_LOADING_MARGIN; color_t color = COLOR_WHITE; color.a = (uint8_t)(alpha * 255.0f); diff --git a/src/dusk/ui/uitextbox.c b/src/dusk/ui/uitextbox.c index aa032803..487c1e9a 100644 --- a/src/dusk/ui/uitextbox.c +++ b/src/dusk/ui/uitextbox.c @@ -32,9 +32,10 @@ errorret_t uiTextboxInit(void) { (float_t)(UI_TEXTBOX_LINES_PER_PAGE_MAX - 1) * UI_TEXTBOX_LINE_SPACING + 2.0f * fontH ); - UI_TEXTBOX.x = 10.0f; - UI_TEXTBOX.y = (float_t)SCREEN.height - tbHeight - 10.0f; - UI_TEXTBOX.width = (float_t)SCREEN.width - 20.0f; + UI_TEXTBOX.x = (float_t)SCREEN.scanX + 10.0f; + UI_TEXTBOX.y = (float_t)(SCREEN.scanY + SCREEN.scanHeight) - + tbHeight - 10.0f; + UI_TEXTBOX.width = (float_t)SCREEN.scanWidth - 20.0f; UI_TEXTBOX.height = tbHeight; UI_TEXTBOX.frame.tileset.columns = 3; -- 2.52.0 From c4969a36cca5040f46fc1e2d858052a6c0dc0fad Mon Sep 17 00:00:00 2001 From: Dominic Masters Date: Thu, 25 Jun 2026 12:13:04 -0500 Subject: [PATCH 02/16] Add UI cropping --- src/dusk/console/console.c | 17 ------- src/dusk/console/console.h | 7 --- src/dusk/display/screen/screen.c | 87 ++++++++++++++++++++------------ src/dusk/display/screen/screen.h | 34 ++++++++++++- src/dusk/ui/CMakeLists.txt | 2 + src/dusk/ui/ui.c | 2 + src/dusk/ui/uiconsole.c | 28 ++++++++++ src/dusk/ui/uiconsole.h | 17 +++++++ src/dusk/ui/uicrop.c | 84 ++++++++++++++++++++++++++++++ src/dusk/ui/uicrop.h | 30 +++++++++++ src/dusk/ui/uielement.c | 8 ++- src/dusk/ui/uiplayerpos.c | 8 ++- 12 files changed, 263 insertions(+), 61 deletions(-) create mode 100644 src/dusk/ui/uiconsole.c create mode 100644 src/dusk/ui/uiconsole.h create mode 100644 src/dusk/ui/uicrop.c create mode 100644 src/dusk/ui/uicrop.h diff --git a/src/dusk/console/console.c b/src/dusk/console/console.c index d712a48e..fe6e6119 100644 --- a/src/dusk/console/console.c +++ b/src/dusk/console/console.c @@ -12,9 +12,6 @@ #include "input/input.h" #include "log/log.h" #include "engine/engine.h" -#include "display/shader/shaderunlit.h" -#include "display/text/text.h" -#include "display/spritebatch/spritebatch.h" console_t CONSOLE; @@ -63,20 +60,6 @@ void consoleUpdate(void) { } } -errorret_t consoleDraw(void) { - if(!CONSOLE.visible) errorOk(); - - for(uint32_t i = 0; i < CONSOLE_HISTORY_MAX; i++) { - errorChain(textDraw( - 0, FONT_DEFAULT.tileset->tileHeight * i, - CONSOLE.line[i], - COLOR_RED, - &FONT_DEFAULT - )); - } - return spriteBatchFlush(); -} - void consoleDispose(void) { #ifdef DUSK_CONSOLE_POSIX threadMutexDispose(&CONSOLE.printMutex); diff --git a/src/dusk/console/console.h b/src/dusk/console/console.h index 309714b4..7ac6378b 100644 --- a/src/dusk/console/console.h +++ b/src/dusk/console/console.h @@ -45,13 +45,6 @@ void consolePrint(const char_t *message, ...); */ void consoleUpdate(void); -/** - * Renders the console history to the screen (UI space). - * - * @return The error return value. - */ -errorret_t consoleDraw(void); - /** * Disposes of the console. */ diff --git a/src/dusk/display/screen/screen.c b/src/dusk/display/screen/screen.c index 9e2e7d10..79920a12 100644 --- a/src/dusk/display/screen/screen.c +++ b/src/dusk/display/screen/screen.c @@ -13,6 +13,13 @@ screen_t SCREEN; +const screencropaspectinfo_t SCREEN_CROP_ASPECTS[SCREEN_CROP_ASPECT_COUNT] = { + [SCREEN_CROP_ASPECT_4_3] = { .ratio = 4.0f / 3.0f }, + [SCREEN_CROP_ASPECT_3_2] = { .ratio = 3.0f / 2.0f }, + [SCREEN_CROP_ASPECT_16_10] = { .ratio = 16.0f / 10.0f }, + [SCREEN_CROP_ASPECT_16_9] = { .ratio = 16.0f / 9.0f }, +}; + errorret_t screenInit() { memoryZero(&SCREEN, sizeof(screen_t)); @@ -53,10 +60,7 @@ errorret_t screenBind() { SCREEN.width = frameBufferGetWidth(FRAMEBUFFER_BOUND); SCREEN.height = frameBufferGetHeight(FRAMEBUFFER_BOUND); SCREEN.aspect = frameBufferGetAspect(FRAMEBUFFER_BOUND); - SCREEN.scanX = 0; - SCREEN.scanY = 0; - SCREEN.scanWidth = SCREEN.width; - SCREEN.scanHeight = SCREEN.height; + screenUpdateScan(); // No needd for a framebuffer. #ifdef DUSK_DISPLAY_SIZE_DYNAMIC @@ -73,10 +77,7 @@ errorret_t screenBind() { SCREEN.width = SCREEN.fixedSize.width; SCREEN.height = SCREEN.fixedSize.height; SCREEN.aspect = (float_t)SCREEN.width / (float_t)SCREEN.height; - SCREEN.scanX = 0; - SCREEN.scanY = 0; - SCREEN.scanWidth = SCREEN.width; - SCREEN.scanHeight = SCREEN.height; + screenUpdateScan(); if(SCREEN.framebufferReady) { // Is current framebuffer the correct size? @@ -114,10 +115,7 @@ errorret_t screenBind() { SCREEN.width = fbWidth; SCREEN.height = fbHeight; SCREEN.aspect = (float_t)SCREEN.width / (float_t)SCREEN.height; - SCREEN.scanX = 0; - SCREEN.scanY = 0; - SCREEN.scanWidth = SCREEN.width; - SCREEN.scanHeight = SCREEN.height; + screenUpdateScan(); if(SCREEN.framebufferReady) { errorChain(frameBufferDispose(&SCREEN.framebuffer)); @@ -148,10 +146,7 @@ errorret_t screenBind() { SCREEN.width = newFbWidth; SCREEN.height = newFbHeight; SCREEN.aspect = curFbAspect; - SCREEN.scanX = 0; - SCREEN.scanY = 0; - SCREEN.scanWidth = SCREEN.width; - SCREEN.scanHeight = SCREEN.height; + screenUpdateScan(); errorChain(frameBufferBind(&SCREEN.framebuffer)); errorOk(); } @@ -168,10 +163,7 @@ errorret_t screenBind() { SCREEN.width = newFbWidth; SCREEN.height = newFbHeight; SCREEN.aspect = (float_t)SCREEN.width / (float_t)SCREEN.height; - SCREEN.scanX = 0; - SCREEN.scanY = 0; - SCREEN.scanWidth = SCREEN.width; - SCREEN.scanHeight = SCREEN.height; + screenUpdateScan(); SCREEN.framebufferReady = true; // Bind FB @@ -191,10 +183,7 @@ errorret_t screenBind() { SCREEN.width = newFbWidth; SCREEN.height = newFbHeight; SCREEN.aspect = (float_t)SCREEN.width / (float_t)SCREEN.height; - SCREEN.scanX = 0; - SCREEN.scanY = 0; - SCREEN.scanWidth = SCREEN.width; - SCREEN.scanHeight = SCREEN.height; + screenUpdateScan(); if(fbWidth == newFbWidth && fbHeight == newFbHeight) { // No need to use framebuffer. @@ -241,10 +230,7 @@ errorret_t screenBind() { SCREEN.width = newFbWidth; SCREEN.height = newFbHeight; SCREEN.aspect = (float_t)SCREEN.width / (float_t)SCREEN.height; - SCREEN.scanX = 0; - SCREEN.scanY = 0; - SCREEN.scanWidth = SCREEN.width; - SCREEN.scanHeight = SCREEN.height; + screenUpdateScan(); if(fbWidth == newFbWidth && fbHeight == newFbHeight) { // No need to use framebuffer. @@ -286,10 +272,7 @@ errorret_t screenBind() { float_t fbAspect = fbWidth / fbHeight; SCREEN.width = (int32_t)floorf(SCREEN.height * fbAspect); SCREEN.aspect = (float_t)SCREEN.width / (float_t)SCREEN.height; - SCREEN.scanX = 0; - SCREEN.scanY = 0; - SCREEN.scanWidth = SCREEN.width; - SCREEN.scanHeight = SCREEN.height; + screenUpdateScan(); break; } @@ -423,6 +406,46 @@ errorret_t screenRender() { errorThrow("Invalid screen mode."); } +void screenUpdateScan(void) { + SCREEN.scanX = 0; + SCREEN.scanY = 0; + SCREEN.scanWidth = SCREEN.width; + SCREEN.scanHeight = SCREEN.height; + + #ifdef DUSK_DISPLAY_SIZE_DYNAMIC + // Find the smallest standard ratio >= the screen aspect. + float_t targetRatio = -1.0f; + for(int32_t i = 0; i < SCREEN_CROP_ASPECT_COUNT; i++) { + if(SCREEN_CROP_ASPECTS[i].ratio >= SCREEN.aspect) { + targetRatio = SCREEN_CROP_ASPECTS[i].ratio; + break; + } + } + if(targetRatio < 0.0f) { + // Wider than all entries: cap at last (16:9). + targetRatio = SCREEN_CROP_ASPECTS[ + SCREEN_CROP_ASPECT_COUNT - 1 + ].ratio; + } + + if(targetRatio > SCREEN.aspect) { + // Letterbox: target is wider, restrict height. + int32_t targetH = (int32_t)floorf( + (float_t)SCREEN.width / targetRatio + ); + SCREEN.scanY = (SCREEN.height - targetH) / 2; + SCREEN.scanHeight = targetH; + } else if(targetRatio < SCREEN.aspect) { + // Pillarbox: target is narrower, restrict width. + int32_t targetW = (int32_t)floorf( + (float_t)SCREEN.height * targetRatio + ); + SCREEN.scanX = (SCREEN.width - targetW) / 2; + SCREEN.scanWidth = targetW; + } + #endif +} + errorret_t screenDispose() { #ifdef DUSK_DISPLAY_SIZE_DYNAMIC if(SCREEN.framebufferReady) { diff --git a/src/dusk/display/screen/screen.h b/src/dusk/display/screen/screen.h index bf3a26a6..1213354f 100644 --- a/src/dusk/display/screen/screen.h +++ b/src/dusk/display/screen/screen.h @@ -17,6 +17,27 @@ #endif #endif +typedef enum { + SCREEN_CROP_ASPECT_4_3, + SCREEN_CROP_ASPECT_3_2, + SCREEN_CROP_ASPECT_16_10, + SCREEN_CROP_ASPECT_16_9, + SCREEN_CROP_ASPECT_COUNT +} screencropaspect_t; + +typedef struct { + float_t ratio; +} screencropaspectinfo_t; + +/** + * Standard crop aspect ratios, sorted narrowest to widest. + * screenUpdateScan() selects the smallest ratio >= the current + * screen aspect; screens wider than the widest entry are capped + * at the last entry (16:9). + */ +extern const screencropaspectinfo_t + SCREEN_CROP_ASPECTS[SCREEN_CROP_ASPECT_COUNT]; + typedef enum { SCREEN_MODE_BACKBUFFER, @@ -116,9 +137,20 @@ errorret_t screenUnbind(); */ errorret_t screenRender(); +/** + * Updates the scan area for the current screen dimensions. + * Defaults to the full viewport. On dynamic displays, finds the + * smallest SCREEN_CROP_ASPECTS entry whose ratio is >= the + * screen aspect and centers a crop window of that ratio. + * Screens narrower than the target are letterboxed; screens + * wider than the target are pillarboxed. Screens wider than + * all entries are pillarboxed to the last entry (16:9). + */ +void screenUpdateScan(void); + /** * Disposes the screen system. - * + * * @return Error code and state, if error occurs. */ errorret_t screenDispose(); \ No newline at end of file diff --git a/src/dusk/ui/CMakeLists.txt b/src/dusk/ui/CMakeLists.txt index ce5eae43..73801f42 100644 --- a/src/dusk/ui/CMakeLists.txt +++ b/src/dusk/ui/CMakeLists.txt @@ -7,6 +7,8 @@ target_sources(${DUSK_LIBRARY_TARGET_NAME} PUBLIC ui.c + uiconsole.c + uicrop.c uifps.c uielement.c uiframe.c diff --git a/src/dusk/ui/ui.c b/src/dusk/ui/ui.c index 6310ee13..182e9e4a 100644 --- a/src/dusk/ui/ui.c +++ b/src/dusk/ui/ui.c @@ -13,6 +13,7 @@ #include "ui/uielement.h" #include "ui/uifullbox.h" #include "ui/uiloading.h" +#include "ui/uicrop.h" #include "time/time.h" #include "log/log.h" @@ -20,6 +21,7 @@ ui_t UI; errorret_t uiInit(void) { memoryZero(&UI, sizeof(ui_t)); + uiCropInit(); uiFullboxInit(&UI_FULLBOX_UNDER); uiFullboxInit(&UI_FULLBOX_OVER); uiLoadingInit(); diff --git a/src/dusk/ui/uiconsole.c b/src/dusk/ui/uiconsole.c new file mode 100644 index 00000000..549cf000 --- /dev/null +++ b/src/dusk/ui/uiconsole.c @@ -0,0 +1,28 @@ +/** + * Copyright (c) 2026 Dominic Masters + * + * This software is released under the MIT License. + * https://opensource.org/licenses/MIT + */ + +#include "uiconsole.h" +#include "console/console.h" +#include "display/screen/screen.h" +#include "display/text/text.h" +#include "display/spritebatch/spritebatch.h" + +errorret_t uiConsoleDraw(void) { + if(!CONSOLE.visible) errorOk(); + + float_t lineH = (float_t)FONT_DEFAULT.tileset->tileHeight; + for(uint32_t i = 0; i < CONSOLE_HISTORY_MAX; i++) { + errorChain(textDraw( + (float_t)SCREEN.scanX, + (float_t)SCREEN.scanY + lineH * (float_t)i, + CONSOLE.line[i], + COLOR_RED, + &FONT_DEFAULT + )); + } + return spriteBatchFlush(); +} diff --git a/src/dusk/ui/uiconsole.h b/src/dusk/ui/uiconsole.h new file mode 100644 index 00000000..2a227f74 --- /dev/null +++ b/src/dusk/ui/uiconsole.h @@ -0,0 +1,17 @@ +/** + * Copyright (c) 2026 Dominic Masters + * + * This software is released under the MIT License. + * https://opensource.org/licenses/MIT + */ + +#pragma once +#include "error/error.h" + +/** + * Renders the console history into the scan-safe area. + * No-ops when the console is not visible. + * + * @return Any error that occurs. + */ +errorret_t uiConsoleDraw(void); diff --git a/src/dusk/ui/uicrop.c b/src/dusk/ui/uicrop.c new file mode 100644 index 00000000..c3a2f016 --- /dev/null +++ b/src/dusk/ui/uicrop.c @@ -0,0 +1,84 @@ +/** + * Copyright (c) 2026 Dominic Masters + * + * This software is released under the MIT License. + * https://opensource.org/licenses/MIT + */ + +#include "uicrop.h" +#include "display/screen/screen.h" +#include "display/spritebatch/spritebatch.h" +#include "display/shader/shaderunlit.h" +#include "display/texture/texture.h" + +uicrop_t UI_CROP; + +void uiCropInit(void) { + UI_CROP.color = COLOR_BLACK; +} + +errorret_t uiCropDraw(void) { + if( + SCREEN.scanX == 0 && + SCREEN.scanY == 0 && + SCREEN.scanWidth == SCREEN.width && + SCREEN.scanHeight == SCREEN.height + ) errorOk(); + + float_t x0 = (float_t)SCREEN.scanX; + float_t y0 = (float_t)SCREEN.scanY; + float_t x1 = (float_t)(SCREEN.scanX + SCREEN.scanWidth); + float_t y1 = (float_t)(SCREEN.scanY + SCREEN.scanHeight); + float_t w = (float_t)SCREEN.width; + float_t h = (float_t)SCREEN.height; + + spritebatchsprite_t sprites[4]; + int32_t count = 0; + + if(SCREEN.scanX > 0) { + sprites[count++] = (spritebatchsprite_t){ + .min = { 0.0f, 0.0f, 0.0f }, + .max = { x0, h, 0.0f }, + .uvMin = { 0.0f, 0.0f }, + .uvMax = { 1.0f, 1.0f } + }; + } + + if(SCREEN.scanX + SCREEN.scanWidth < SCREEN.width) { + sprites[count++] = (spritebatchsprite_t){ + .min = { x1, 0.0f, 0.0f }, + .max = { w, h, 0.0f }, + .uvMin = { 0.0f, 0.0f }, + .uvMax = { 1.0f, 1.0f } + }; + } + + if(SCREEN.scanY > 0) { + sprites[count++] = (spritebatchsprite_t){ + .min = { x0, 0.0f, 0.0f }, + .max = { x1, y0, 0.0f }, + .uvMin = { 0.0f, 0.0f }, + .uvMax = { 1.0f, 1.0f } + }; + } + + if(SCREEN.scanY + SCREEN.scanHeight < SCREEN.height) { + sprites[count++] = (spritebatchsprite_t){ + .min = { x0, y1, 0.0f }, + .max = { x1, h, 0.0f }, + .uvMin = { 0.0f, 0.0f }, + .uvMax = { 1.0f, 1.0f } + }; + } + + if(count == 0) errorOk(); + + shadermaterial_t material = { + .unlit = { + .color = UI_CROP.color, + .texture = &TEXTURE_WHITE + } + }; + errorChain(spriteBatchBuffer(sprites, count, &SHADER_UNLIT, material)); + return spriteBatchFlush(); +} diff --git a/src/dusk/ui/uicrop.h b/src/dusk/ui/uicrop.h new file mode 100644 index 00000000..c0458583 --- /dev/null +++ b/src/dusk/ui/uicrop.h @@ -0,0 +1,30 @@ +/** + * Copyright (c) 2026 Dominic Masters + * + * This software is released under the MIT License. + * https://opensource.org/licenses/MIT + */ + +#pragma once +#include "error/error.h" +#include "display/color.h" + +typedef struct { + color_t color; +} uicrop_t; + +extern uicrop_t UI_CROP; + +/** + * Initializes the crop bars to opaque black. + */ +void uiCropInit(void); + +/** + * Renders solid-color bars covering every area outside the + * current scan-safe region. No-ops when the scan area equals + * the full viewport. + * + * @return Any error that occurs. + */ +errorret_t uiCropDraw(void); diff --git a/src/dusk/ui/uielement.c b/src/dusk/ui/uielement.c index 7e8b8415..2849e4ff 100644 --- a/src/dusk/ui/uielement.c +++ b/src/dusk/ui/uielement.c @@ -7,15 +7,19 @@ #include "uielement.h" #include "assert/assert.h" -#include "console/console.h" #include "ui/uifps.h" #include "engine/engine.h" #include "ui/uitextbox.h" #include "ui/uifullbox.h" #include "ui/uiloading.h" #include "ui/uiplayerpos.h" +#include "ui/uicrop.h" +#include "ui/uiconsole.h" uielement_t UI_ELEMENTS[] = { + // Crop bars: black bars outside the scan-safe area. + { .type = UI_ELEMENT_TYPE_NATIVE, .draw = uiCropDraw }, + // Fullbox under: above scene, below system UI. { .type = UI_ELEMENT_TYPE_NATIVE, .draw = uiFullboxUnderDraw }, @@ -27,7 +31,7 @@ uielement_t UI_ELEMENTS[] = { // These render above the fullbox overlay. - { .type = UI_ELEMENT_TYPE_NATIVE, .draw = consoleDraw }, + { .type = UI_ELEMENT_TYPE_NATIVE, .draw = uiConsoleDraw }, { .type = UI_ELEMENT_TYPE_NATIVE, .draw = uiFPSDraw }, { .type = UI_ELEMENT_TYPE_NATIVE, .draw = uiPlayerPosDraw }, { .type = UI_ELEMENT_TYPE_NATIVE, .draw = uiLoadingDraw }, diff --git a/src/dusk/ui/uiplayerpos.c b/src/dusk/ui/uiplayerpos.c index dd6a17a5..29cc8a75 100644 --- a/src/dusk/ui/uiplayerpos.c +++ b/src/dusk/ui/uiplayerpos.c @@ -6,6 +6,7 @@ */ #include "uiplayerpos.h" +#include "display/screen/screen.h" #include "display/text/text.h" #include "display/spritebatch/spritebatch.h" #include "rpg/entity/entity.h" @@ -38,7 +39,10 @@ errorret_t uiPlayerPosDraw() { (int_t)chunkPos.z ); - float_t y = (float_t)FONT_DEFAULT.tileset->tileHeight; - errorChain(textDraw(0, y, text, COLOR_GREEN, &FONT_DEFAULT)); + float_t y = (float_t)SCREEN.scanY + + (float_t)FONT_DEFAULT.tileset->tileHeight; + errorChain(textDraw( + (float_t)SCREEN.scanX, y, text, COLOR_GREEN, &FONT_DEFAULT + )); return spriteBatchFlush(); } -- 2.52.0 From 8faf881399a8006aa290191ee20cf2612b97669b Mon Sep 17 00:00:00 2001 From: Dominic Masters Date: Thu, 25 Jun 2026 14:56:48 -0500 Subject: [PATCH 03/16] Bit if UI cleanup --- src/dusk/engine/engine.c | 8 +- src/dusk/input/input.c | 56 ++++++++ src/dusk/input/input.csv | 1 + src/dusk/input/input.h | 78 ++++++++++- src/dusk/ui/CMakeLists.txt | 6 +- src/dusk/ui/debug/CMakeLists.txt | 11 ++ src/dusk/ui/{ => debug}/uiconsole.c | 0 src/dusk/ui/{ => debug}/uiconsole.h | 0 src/dusk/ui/{ => debug}/uifps.c | 0 src/dusk/ui/{ => debug}/uifps.h | 0 src/dusk/ui/{ => debug}/uiplayerpos.c | 0 src/dusk/ui/{ => debug}/uiplayerpos.h | 0 src/dusk/ui/ui.c | 36 +++--- src/dusk/ui/ui.h | 8 +- src/dusk/ui/uicrop.c | 3 +- src/dusk/ui/uicrop.h | 4 +- src/dusk/ui/uielement.c | 71 ++++++---- src/dusk/ui/uielement.h | 37 ++++-- src/dusk/ui/uifocus/CMakeLists.txt | 9 ++ src/dusk/ui/uifocus/uifocus.c | 179 ++++++++++++++++++++++++++ src/dusk/ui/uifocus/uifocus.h | 122 ++++++++++++++++++ src/dusk/ui/uifocus/uifocusitem.h | 32 +++++ src/dusk/ui/uifullbox.c | 21 +++ src/dusk/ui/uifullbox.h | 32 +++++ src/dusk/ui/uiloading.c | 19 +-- src/dusk/ui/uiloading.h | 8 +- src/dusk/ui/uitextbox.c | 3 +- src/dusk/ui/uitextbox.h | 4 +- src/duskdolphin/input/inputdolphin.c | 2 + src/dusklinux/input/inputlinux.c | 2 + src/duskpsp/input/inputpsp.c | 1 + 31 files changed, 676 insertions(+), 77 deletions(-) create mode 100644 src/dusk/ui/debug/CMakeLists.txt rename src/dusk/ui/{ => debug}/uiconsole.c (100%) rename src/dusk/ui/{ => debug}/uiconsole.h (100%) rename src/dusk/ui/{ => debug}/uifps.c (100%) rename src/dusk/ui/{ => debug}/uifps.h (100%) rename src/dusk/ui/{ => debug}/uiplayerpos.c (100%) rename src/dusk/ui/{ => debug}/uiplayerpos.h (100%) create mode 100644 src/dusk/ui/uifocus/CMakeLists.txt create mode 100644 src/dusk/ui/uifocus/uifocus.c create mode 100644 src/dusk/ui/uifocus/uifocus.h create mode 100644 src/dusk/ui/uifocus/uifocusitem.h diff --git a/src/dusk/engine/engine.c b/src/dusk/engine/engine.c index 9ce6efd5..9e110efb 100644 --- a/src/dusk/engine/engine.c +++ b/src/dusk/engine/engine.c @@ -16,7 +16,6 @@ #include "cutscene/cutscene.h" #include "asset/asset.h" #include "ui/ui.h" -#include "ui/uitextbox.h" #include "assert/assert.h" #include "network/network.h" #include "system/system.h" @@ -43,7 +42,6 @@ errorret_t engineInit(const int32_t argc, const char_t **argv) { errorChain(localeManagerInit()); errorChain(displayInit()); errorChain(uiInit()); - errorChain(uiTextboxInit()); errorChain(cutsceneInit()); errorChain(rpgInit()); errorChain(networkInit()); @@ -63,8 +61,7 @@ errorret_t engineUpdate(void) { inputUpdate(); consoleUpdate(); errorChain(rpgUpdate()); - uiUpdate(); - errorChain(uiTextboxUpdate()); + errorChain(uiUpdate()); errorChain(cutsceneUpdate()); errorChain(sceneUpdate()); errorChain(assetUpdate()); @@ -80,13 +77,12 @@ void engineExit(void) { } errorret_t engineDispose(void) { - uiTextboxDispose(); cutsceneDispose(); errorChain(sceneDispose()); errorChain(networkDispose()); errorChain(rpgDispose()); localeManagerDispose(); - uiDispose(); + errorChain(uiDispose()); consoleDispose(); errorChain(displayDispose()); // errorChain(saveDispose()); diff --git a/src/dusk/input/input.c b/src/dusk/input/input.c index 9c65586c..060b9962 100644 --- a/src/dusk/input/input.c +++ b/src/dusk/input/input.c @@ -139,6 +139,62 @@ float_t inputGetLastValue(const inputaction_t action) { assertTrue(action < INPUT_ACTION_COUNT, "Input action out of bounds"); return INPUT.actions[action].lastDynamicValue; } + + bool_t inputIsDownDynamic(const inputaction_t action) { + return inputGetCurrentValueDynamic(action) > 0.0f; + } + + bool_t inputWasDownDynamic(const inputaction_t action) { + return inputGetLastValueDynamic(action) > 0.0f; + } + + bool_t inputPressedDynamic(const inputaction_t action) { + return inputIsDownDynamic(action) && !inputWasDownDynamic(action); + } + + bool_t inputReleasedDynamic(const inputaction_t action) { + return !inputIsDownDynamic(action) && inputWasDownDynamic(action); + } + + float_t inputAxisDynamic( + const inputaction_t neg, + const inputaction_t pos + ) { + return inputGetCurrentValueDynamic(pos) - + inputGetCurrentValueDynamic(neg); + } + + void inputAxis2DDynamic( + const inputaction_t negX, const inputaction_t posX, + const inputaction_t negY, const inputaction_t posY, + vec2 result + ) { + assertNotNull(result, "Result vector cannot be null"); + result[0] = inputAxisDynamic(negX, posX); + result[1] = inputAxisDynamic(negY, posY); + } + + void inputAngle2DDynamic( + const inputaction_t negX, const inputaction_t posX, + const inputaction_t negY, const inputaction_t posY, + vec2 result + ) { + assertNotNull(result, "Result vector cannot be null"); + float_t x = inputAxisDynamic(negX, posX); + float_t y = inputAxisDynamic(negY, posY); + float_t mag = sqrtf(x * x + y * y); + if(mag <= 0.0f) { + result[0] = 0.0f; + result[1] = 0.0f; + return; + } + if(mag > 1.0f) { + x /= mag; + y /= mag; + } + result[0] = x; + result[1] = y; + } #endif bool_t inputIsDown(const inputaction_t action) { diff --git a/src/dusk/input/input.csv b/src/dusk/input/input.csv index 42555928..2fa6a46e 100644 --- a/src/dusk/input/input.csv +++ b/src/dusk/input/input.csv @@ -5,6 +5,7 @@ LEFT, RIGHT, ACCEPT, CANCEL, +PAUSE, RAGEQUIT, CONSOLE, POINTERX, diff --git a/src/dusk/input/input.h b/src/dusk/input/input.h index 2ecbbdc8..bf3238a8 100644 --- a/src/dusk/input/input.h +++ b/src/dusk/input/input.h @@ -52,7 +52,7 @@ float_t inputGetLastValue(const inputaction_t action); #ifdef DUSK_TIME_DYNAMIC /** * Gets the current value of a specific input action (dynamic timestep). - * + * * @param action The input action to get the value for. * @return The current value of the action (0.0f to 1.0f). */ @@ -60,11 +60,85 @@ float_t inputGetLastValue(const inputaction_t action); /** * Gets the last value of a specific input action (dynamic timestep). - * + * * @param action The input action to get the value for. * @return The last value of the action (0.0f to 1.0f). */ float_t inputGetLastValueDynamic(const inputaction_t action); + + /** + * Checks if an action is currently down on the dynamic timestep. + * + * @param action The input action to check. + * @return true if the action is currently down. + */ + bool_t inputIsDownDynamic(const inputaction_t action); + + /** + * Checks if an action was down on the previous dynamic frame. + * + * @param action The input action to check. + * @return true if the action was down last dynamic frame. + */ + bool_t inputWasDownDynamic(const inputaction_t action); + + /** + * Checks if an action was pressed this dynamic frame (down now, not before). + * + * @param action The input action to check. + * @return true if the action was just pressed this dynamic frame. + */ + bool_t inputPressedDynamic(const inputaction_t action); + + /** + * Checks if an action was released this dynamic frame (up now, down before). + * + * @param action The input action to check. + * @return true if the action was just released this dynamic frame. + */ + bool_t inputReleasedDynamic(const inputaction_t action); + + /** + * Gets the value of an input axis on the dynamic timestep. + * + * @param neg The action representing the negative direction. + * @param pos The action representing the positive direction. + * @return The current axis value (-1.0f to 1.0f). + */ + float_t inputAxisDynamic( + const inputaction_t neg, + const inputaction_t pos + ); + + /** + * Gets the values of a 2D input axis on the dynamic timestep. + * + * @param negX Negative X action. + * @param posX Positive X action. + * @param negY Negative Y action. + * @param posY Positive Y action. + * @param result vec2 to store the result (-1.0f to 1.0f per axis). + */ + void inputAxis2DDynamic( + const inputaction_t negX, const inputaction_t posX, + const inputaction_t negY, const inputaction_t posY, + vec2 result + ); + + /** + * Gets an angled 2D unit vector from four actions on the dynamic timestep. + * + * @param negX Negative X action. + * @param posX Positive X action. + * @param negY Negative Y action. + * @param posY Positive Y action. + * @param result vec2 to store the result. + */ + void inputAngle2DDynamic( + const inputaction_t negX, const inputaction_t posX, + const inputaction_t negY, const inputaction_t posY, + vec2 result + ); #endif /** diff --git a/src/dusk/ui/CMakeLists.txt b/src/dusk/ui/CMakeLists.txt index 73801f42..9fe2e9c8 100644 --- a/src/dusk/ui/CMakeLists.txt +++ b/src/dusk/ui/CMakeLists.txt @@ -3,17 +3,17 @@ # This software is released under the MIT License. # https://opensource.org/licenses/MIT +add_subdirectory(debug) +add_subdirectory(uifocus) + # Sources target_sources(${DUSK_LIBRARY_TARGET_NAME} PUBLIC ui.c - uiconsole.c uicrop.c - uifps.c uielement.c uiframe.c uifullbox.c uiloading.c uitextbox.c - uiplayerpos.c ) \ No newline at end of file diff --git a/src/dusk/ui/debug/CMakeLists.txt b/src/dusk/ui/debug/CMakeLists.txt new file mode 100644 index 00000000..74e31662 --- /dev/null +++ b/src/dusk/ui/debug/CMakeLists.txt @@ -0,0 +1,11 @@ +# Copyright (c) 2026 Dominic Masters +# +# This software is released under the MIT License. +# https://opensource.org/licenses/MIT + +target_sources(${DUSK_LIBRARY_TARGET_NAME} + PUBLIC + uiconsole.c + uifps.c + uiplayerpos.c +) diff --git a/src/dusk/ui/uiconsole.c b/src/dusk/ui/debug/uiconsole.c similarity index 100% rename from src/dusk/ui/uiconsole.c rename to src/dusk/ui/debug/uiconsole.c diff --git a/src/dusk/ui/uiconsole.h b/src/dusk/ui/debug/uiconsole.h similarity index 100% rename from src/dusk/ui/uiconsole.h rename to src/dusk/ui/debug/uiconsole.h diff --git a/src/dusk/ui/uifps.c b/src/dusk/ui/debug/uifps.c similarity index 100% rename from src/dusk/ui/uifps.c rename to src/dusk/ui/debug/uifps.c diff --git a/src/dusk/ui/uifps.h b/src/dusk/ui/debug/uifps.h similarity index 100% rename from src/dusk/ui/uifps.h rename to src/dusk/ui/debug/uifps.h diff --git a/src/dusk/ui/uiplayerpos.c b/src/dusk/ui/debug/uiplayerpos.c similarity index 100% rename from src/dusk/ui/uiplayerpos.c rename to src/dusk/ui/debug/uiplayerpos.c diff --git a/src/dusk/ui/uiplayerpos.h b/src/dusk/ui/debug/uiplayerpos.h similarity index 100% rename from src/dusk/ui/uiplayerpos.h rename to src/dusk/ui/debug/uiplayerpos.h diff --git a/src/dusk/ui/ui.c b/src/dusk/ui/ui.c index 182e9e4a..f069dfd5 100644 --- a/src/dusk/ui/ui.c +++ b/src/dusk/ui/ui.c @@ -11,23 +11,17 @@ #include "display/spritebatch/spritebatch.h" #include "display/screen/screen.h" #include "ui/uielement.h" -#include "ui/uifullbox.h" -#include "ui/uiloading.h" -#include "ui/uicrop.h" -#include "time/time.h" +#include "ui/uifocus/uifocus.h" #include "log/log.h" ui_t UI; errorret_t uiInit(void) { memoryZero(&UI, sizeof(ui_t)); - uiCropInit(); - uiFullboxInit(&UI_FULLBOX_UNDER); - uiFullboxInit(&UI_FULLBOX_OVER); - uiLoadingInit(); + uiFocusInit(); uielement_t *element = &UI_ELEMENTS[0]; - while(element->type != UI_ELEMENT_TYPE_NULL) { + while(element->draw != NULL) { errorChain(uiElementInit(element)); element++; } @@ -35,15 +29,21 @@ errorret_t uiInit(void) { errorOk(); } -void uiUpdate(void) { - uiFullboxUpdate(&UI_FULLBOX_UNDER, TIME.delta); - uiFullboxUpdate(&UI_FULLBOX_OVER, TIME.delta); - uiLoadingUpdate(TIME.delta); +errorret_t uiUpdate(void) { + uiFocusUpdate(); + + uielement_t *element = &UI_ELEMENTS[0]; + while(element->draw != NULL) { + errorChain(uiElementUpdate(element)); + element++; + } + + errorOk(); } errorret_t uiRender(void) { const uielement_t *element = &UI_ELEMENTS[0]; - while(element->type != UI_ELEMENT_TYPE_NULL) { + while(element->draw != NULL) { errorChain(uiElementDraw(element)); if(SPRITEBATCH.spriteCount > 0) { @@ -56,5 +56,11 @@ errorret_t uiRender(void) { errorOk(); } -void uiDispose(void) { +errorret_t uiDispose(void) { + uielement_t *element = &UI_ELEMENTS[0]; + while(element->draw != NULL) { + errorChain(uiElementDispose(element)); + element++; + } + errorOk(); } \ No newline at end of file diff --git a/src/dusk/ui/ui.h b/src/dusk/ui/ui.h index 1d5ab848..2887a262 100644 --- a/src/dusk/ui/ui.h +++ b/src/dusk/ui/ui.h @@ -21,8 +21,10 @@ errorret_t uiInit(void); /** * Updates the UI system. + * + * @return Any error that occurs. */ -void uiUpdate(void); +errorret_t uiUpdate(void); /** * Renders the UI system. @@ -33,5 +35,7 @@ errorret_t uiRender(void); /** * Disposes of the UI system. + * + * @return Any error that occurs. */ -void uiDispose(void); \ No newline at end of file +errorret_t uiDispose(void); \ No newline at end of file diff --git a/src/dusk/ui/uicrop.c b/src/dusk/ui/uicrop.c index c3a2f016..f37bf310 100644 --- a/src/dusk/ui/uicrop.c +++ b/src/dusk/ui/uicrop.c @@ -13,8 +13,9 @@ uicrop_t UI_CROP; -void uiCropInit(void) { +errorret_t uiCropInit(void) { UI_CROP.color = COLOR_BLACK; + errorOk(); } errorret_t uiCropDraw(void) { diff --git a/src/dusk/ui/uicrop.h b/src/dusk/ui/uicrop.h index c0458583..ef822633 100644 --- a/src/dusk/ui/uicrop.h +++ b/src/dusk/ui/uicrop.h @@ -17,8 +17,10 @@ extern uicrop_t UI_CROP; /** * Initializes the crop bars to opaque black. + * + * @return Any error that occurs. */ -void uiCropInit(void); +errorret_t uiCropInit(void); /** * Renders solid-color bars covering every area outside the diff --git a/src/dusk/ui/uielement.c b/src/dusk/ui/uielement.c index 2849e4ff..ce88bc61 100644 --- a/src/dusk/ui/uielement.c +++ b/src/dusk/ui/uielement.c @@ -7,54 +7,79 @@ #include "uielement.h" #include "assert/assert.h" -#include "ui/uifps.h" +#include "ui/debug/uifps.h" #include "engine/engine.h" #include "ui/uitextbox.h" #include "ui/uifullbox.h" #include "ui/uiloading.h" -#include "ui/uiplayerpos.h" +#include "ui/debug/uiplayerpos.h" #include "ui/uicrop.h" -#include "ui/uiconsole.h" +#include "ui/debug/uiconsole.h" uielement_t UI_ELEMENTS[] = { - // Crop bars: black bars outside the scan-safe area. - { .type = UI_ELEMENT_TYPE_NATIVE, .draw = uiCropDraw }, // Fullbox under: above scene, below system UI. - { .type = UI_ELEMENT_TYPE_NATIVE, .draw = uiFullboxUnderDraw }, + { + .init = uiFullboxUnderInit, + .update = uiFullboxUnderUpdate, + .draw = uiFullboxUnderDraw + }, // { .type = UI_ELEMENT_TYPE_SCRIPT, .script = { .script = "ui/test.js" } }, - { .type = UI_ELEMENT_TYPE_NATIVE, .draw = uiTextboxDraw }, + { + .init = uiTextboxInit, + .update = uiTextboxUpdate, + .draw = uiTextboxDraw, + .dispose = uiTextboxDispose + }, + // Fullbox over: above absolutely everything. - { .type = UI_ELEMENT_TYPE_NATIVE, .draw = uiFullboxOverDraw }, + { + .init = uiFullboxOverInit, + .update = uiFullboxOverUpdate, + .draw = uiFullboxOverDraw + }, + { + .init = uiLoadingInit, + .update = uiLoadingUpdate, + .draw = uiLoadingDraw + }, - // These render above the fullbox overlay. - { .type = UI_ELEMENT_TYPE_NATIVE, .draw = uiConsoleDraw }, - { .type = UI_ELEMENT_TYPE_NATIVE, .draw = uiFPSDraw }, - { .type = UI_ELEMENT_TYPE_NATIVE, .draw = uiPlayerPosDraw }, - { .type = UI_ELEMENT_TYPE_NATIVE, .draw = uiLoadingDraw }, + { + .init = uiCropInit, + .draw = uiCropDraw + }, - - { .type = UI_ELEMENT_TYPE_NULL }, + // Debug items + { .draw = uiConsoleDraw }, + { .draw = uiFPSDraw }, + { .draw = uiPlayerPosDraw }, + + { 0 } // Null terminator }; errorret_t uiElementInit(uielement_t *element) { assertNotNull(element, "element must not be NULL"); + if(element->init != NULL) errorChain(element->init()); + errorOk(); +} +errorret_t uiElementUpdate(uielement_t *element) { + assertNotNull(element, "element must not be NULL"); + if(element->update != NULL) errorChain(element->update()); errorOk(); } errorret_t uiElementDraw(const uielement_t *element) { - switch(element->type) { - case UI_ELEMENT_TYPE_NATIVE: - errorChain(element->draw()); - break; - - default: - assertUnreachable("Invalid UI element type"); - } + assertNotNull(element, "element must not be NULL"); + assertNotNull(element->draw, "element draw callback must not be NULL"); + return element->draw(); +} +errorret_t uiElementDispose(uielement_t *element) { + assertNotNull(element, "element must not be NULL"); + if(element->dispose != NULL) errorChain(element->dispose()); errorOk(); } \ No newline at end of file diff --git a/src/dusk/ui/uielement.h b/src/dusk/ui/uielement.h index faee742e..43322116 100644 --- a/src/dusk/ui/uielement.h +++ b/src/dusk/ui/uielement.h @@ -8,28 +8,43 @@ #pragma once #include "error/error.h" -typedef enum { - UI_ELEMENT_TYPE_NULL, - UI_ELEMENT_TYPE_NATIVE, - UI_ELEMENT_TYPE_COUNT -} uielementtype_t; - typedef struct { - uielementtype_t type; + errorret_t (*init)(); + errorret_t (*update)(); errorret_t (*draw)(); + errorret_t (*dispose)(); } uielement_t; extern uielement_t UI_ELEMENTS[]; /** - * Initializes a UI element. + * Initializes a UI element, calling its init callback if set. + * + * @param element The element to initialize. + * @return Any error that occurs. */ errorret_t uiElementInit(uielement_t *element); /** - * Draws a UI element. - * + * Updates a UI element, calling its update callback if set. + * + * @param element The element to update. + * @return Any error that occurs. + */ +errorret_t uiElementUpdate(uielement_t *element); + +/** + * Draws a UI element, calling its draw callback if set. + * * @param element The element to render. * @return Any error that occurs. */ -errorret_t uiElementDraw(const uielement_t *element); \ No newline at end of file +errorret_t uiElementDraw(const uielement_t *element); + +/** + * Disposes of a UI element, calling its dispose callback if set. + * + * @param element The element to dispose. + * @return Any error that occurs. + */ +errorret_t uiElementDispose(uielement_t *element); \ No newline at end of file diff --git a/src/dusk/ui/uifocus/CMakeLists.txt b/src/dusk/ui/uifocus/CMakeLists.txt new file mode 100644 index 00000000..819217b7 --- /dev/null +++ b/src/dusk/ui/uifocus/CMakeLists.txt @@ -0,0 +1,9 @@ +# Copyright (c) 2026 Dominic Masters +# +# This software is released under the MIT License. +# https://opensource.org/licenses/MIT + +target_sources(${DUSK_LIBRARY_TARGET_NAME} + PUBLIC + uifocus.c +) diff --git a/src/dusk/ui/uifocus/uifocus.c b/src/dusk/ui/uifocus/uifocus.c new file mode 100644 index 00000000..c28a878b --- /dev/null +++ b/src/dusk/ui/uifocus/uifocus.c @@ -0,0 +1,179 @@ +/** + * Copyright (c) 2026 Dominic Masters + * + * This software is released under the MIT License. + * https://opensource.org/licenses/MIT + */ + +#include "uifocus.h" +#include "assert/assert.h" +#include "util/memory.h" +#include "input/input.h" +#include "time/time.h" + +const uifocusdirmap_t UI_FOCUS_DIR_MAP[] = { + { INPUT_ACTION_UP, UI_FOCUS_DIRECTION_UP, 0, -1 }, + { INPUT_ACTION_DOWN, UI_FOCUS_DIRECTION_DOWN, 0, 1 }, + { INPUT_ACTION_LEFT, UI_FOCUS_DIRECTION_LEFT, -1, 0 }, + { INPUT_ACTION_RIGHT, UI_FOCUS_DIRECTION_RIGHT, 1, 0 }, + { INPUT_ACTION_NULL, UI_FOCUS_DIRECTION_NONE, 0, 0 }, +}; + +uifocus_t UI_FOCUS; + +void uiFocusInit(void) { + memoryZero(&UI_FOCUS, sizeof(uifocus_t)); +} + +void uiFocusPush( + const uint8_t cols, + const uint8_t rows, + uifocusitemcallback_t selected, + uifocusitemcallback_t changed, + uifocusitemcallback_t closed +) { + assertTrue( + UI_FOCUS.count < UI_FOCUS_STACK_MAX, + "UI focus stack overflow" + ); + + uifocusitem_t *item = &UI_FOCUS.items[UI_FOCUS.count]; + memoryZero(item, sizeof(uifocusitem_t)); + item->cols = cols; + item->rows = rows; + item->selected = selected; + item->changed = changed; + item->closed = closed; + UI_FOCUS.count++; +} + +void uiFocusPop(void) { + assertTrue(UI_FOCUS.count > 0, "UI focus stack underflow"); + + uifocusitem_t *item = &UI_FOCUS.items[UI_FOCUS.count - 1]; + if(item->closed != NULL) item->closed(item); + UI_FOCUS.count--; +} + +void uiFocusPopTo(const int32_t depth) { + assertTrue(depth >= 0, "Focus depth must be >= 0"); + assertTrue(depth <= UI_FOCUS.count, "Focus depth exceeds current depth"); + while(UI_FOCUS.count > depth) uiFocusPop(); +} + +void uiFocusSelect(const uint8_t x, const uint8_t y) { + assertTrue(UI_FOCUS.count > 0, "No active focus item"); + uifocusitem_t *item = &UI_FOCUS.items[UI_FOCUS.count - 1]; + assertTrue(item->cols > 0, "Focus item cols must be > 0"); + assertTrue(item->rows > 0, "Focus item rows must be > 0"); + uint8_t newX = x % item->cols; + uint8_t newY = y % item->rows; + + if(item->changed != NULL) { + uint8_t oldX = item->x; + uint8_t oldY = item->y; + item->x = newX; + item->y = newY; + if(!item->changed(item)) { + item->x = oldX; + item->y = oldY; + } + return; + } + + item->x = newX; + item->y = newY; +} + +void uiFocusMoveDirection( + uifocusitem_t *item, + const uifocusdirection_t dir +) { + const uifocusdirmap_t *m = UI_FOCUS_DIR_MAP; + while(m->action != INPUT_ACTION_NULL) { + if(m->direction == dir) { + uint8_t x = (uint8_t)( + (item->x + item->cols + m->dx) % item->cols + ); + uint8_t y = (uint8_t)( + (item->y + item->rows + m->dy) % item->rows + ); + uiFocusSelect(x, y); + return; + } + m++; + } +} + +void uiFocusUpdate(void) { + if(UI_FOCUS.count == 0) return; + uifocusitem_t *item = &UI_FOCUS.items[UI_FOCUS.count - 1]; + + #ifdef DUSK_TIME_DYNAMIC + #define PRESSED(a) inputPressedDynamic(a) + #define IS_DOWN(a) inputIsDownDynamic(a) + #else + #define PRESSED(a) inputPressed(a) + #define IS_DOWN(a) inputIsDown(a) + #endif + + if(PRESSED(INPUT_ACTION_ACCEPT)) { + if(item->selected != NULL) item->selected(item); + goto done; + } + + if(PRESSED(INPUT_ACTION_CANCEL)) { + if(item->closed != NULL && item->closed(item)) { + UI_FOCUS.count--; + } + goto done; + } + + { + const uifocusdirmap_t *m = UI_FOCUS_DIR_MAP; + while(m->action != INPUT_ACTION_NULL) { + if(PRESSED(m->action)) { + UI_FOCUS.direction = m->direction; + UI_FOCUS.timeHeld = 0.0f; + uiFocusMoveDirection(item, m->direction); + goto done; + } + m++; + } + } + + { + bool_t held = false; + const uifocusdirmap_t *m = UI_FOCUS_DIR_MAP; + while(m->action != INPUT_ACTION_NULL) { + if(m->direction == UI_FOCUS.direction) { + held = IS_DOWN(m->action); + break; + } + m++; + } + + if(!held) { + UI_FOCUS.direction = UI_FOCUS_DIRECTION_NONE; + UI_FOCUS.timeHeld = 0.0f; + goto done; + } + } + + #ifdef DUSK_TIME_DYNAMIC + UI_FOCUS.timeHeld += TIME.dynamicDelta; + #else + UI_FOCUS.timeHeld += TIME.delta; + #endif + + if(UI_FOCUS.timeHeld < UI_FOCUS_HOLD_DELAY) goto done; + + if(UI_FOCUS.timeHeld >= UI_FOCUS_HOLD_DELAY + UI_FOCUS_HOLD_REPEAT) { + UI_FOCUS.timeHeld = UI_FOCUS_HOLD_DELAY; + uiFocusMoveDirection(item, UI_FOCUS.direction); + } + +done: + #undef PRESSED + #undef IS_DOWN +} diff --git a/src/dusk/ui/uifocus/uifocus.h b/src/dusk/ui/uifocus/uifocus.h new file mode 100644 index 00000000..24dc6ed7 --- /dev/null +++ b/src/dusk/ui/uifocus/uifocus.h @@ -0,0 +1,122 @@ +/** + * Copyright (c) 2026 Dominic Masters + * + * This software is released under the MIT License. + * https://opensource.org/licenses/MIT + */ + +#pragma once +#include "uifocusitem.h" +#include "input/inputaction.h" + +/** Maximum depth of the focus stack. */ +#define UI_FOCUS_STACK_MAX 8 + +/** + * How long a direction must be held before repeating begins, in seconds. + */ +#define UI_FOCUS_HOLD_DELAY 0.5f + +/** + * Interval between repeated moves while a direction is held, in seconds. + */ +#define UI_FOCUS_HOLD_REPEAT 0.1f + +typedef enum { + UI_FOCUS_DIRECTION_NONE, + UI_FOCUS_DIRECTION_UP, + UI_FOCUS_DIRECTION_DOWN, + UI_FOCUS_DIRECTION_LEFT, + UI_FOCUS_DIRECTION_RIGHT +} uifocusdirection_t; + +typedef struct { + inputaction_t action; + uifocusdirection_t direction; + int8_t dx; + int8_t dy; +} uifocusdirmap_t; + +/** + * Mapping of input actions to focus directions, terminated by an + * entry with action == INPUT_ACTION_NULL. + */ +extern const uifocusdirmap_t UI_FOCUS_DIR_MAP[]; + +/** + * A stack of focused UI items. Push an item when a widget captures + * focus; pop it when focus is released. The topmost item is always + * the active focus context. + */ +typedef struct { + uifocusitem_t items[UI_FOCUS_STACK_MAX]; + int32_t count; + uifocusdirection_t direction; + float_t timeHeld; +} uifocus_t; + +extern uifocus_t UI_FOCUS; + +/** + * Initializes the focus system, zeroing all state. + */ +void uiFocusInit(void); + +/** + * Pushes a new focus item onto the stack with the given grid dimensions + * and callbacks. x and y are initialized to 0. + * + * @param cols Number of columns in the focus grid. + * @param rows Number of rows in the focus grid. + * @param selected Called when the user selects the focused cell. + * @param changed Called when the focused cell position changes. + * @param closed Called when this focus item is popped. + */ +void uiFocusPush( + const uint8_t cols, + const uint8_t rows, + uifocusitemcallback_t selected, + uifocusitemcallback_t changed, + uifocusitemcallback_t closed +); + +/** + * Pops the topmost focus item from the stack, invoking its closed + * callback if one is set. + */ +void uiFocusPop(void); + +/** + * Pops focus items until the stack depth reaches the given value. + * Each popped item has its closed callback invoked as normal. + * + * @param depth Target stack depth to pop back to. + */ +void uiFocusPopTo(const int32_t depth); + +/** + * Manually sets the cursor position of the topmost focus item and + * fires its changed callback. + * + * @param x Column to move to. + * @param y Row to move to. + */ +void uiFocusSelect(const uint8_t x, const uint8_t y); + +/** + * Moves the topmost focus item one step in the given direction, + * wrapping at the grid edges, and fires its changed callback. + * + * @param item The focus item to move. + * @param dir Direction to move. + */ +void uiFocusMoveDirection( + uifocusitem_t *item, + const uifocusdirection_t dir +); + +/** + * Updates the focus system. Handles first-press movement and + * held-direction repeating. Called once per game tick. + */ +void uiFocusUpdate(void); diff --git a/src/dusk/ui/uifocus/uifocusitem.h b/src/dusk/ui/uifocus/uifocusitem.h new file mode 100644 index 00000000..ebe44167 --- /dev/null +++ b/src/dusk/ui/uifocus/uifocusitem.h @@ -0,0 +1,32 @@ +/** + * Copyright (c) 2026 Dominic Masters + * + * This software is released under the MIT License. + * https://opensource.org/licenses/MIT + */ + +#pragma once +#include "dusk.h" + +typedef struct uifocusitem_s uifocusitem_t; + +/** + * Callback invoked when a focus item's selected cell changes. + * + * @param item The focus item that changed. + */ +typedef bool_t (*uifocusitemcallback_t)(const uifocusitem_t *item); + +/** + * A single entry on the UI focus stack. Tracks the focused cell + * within a grid of cols x rows, and the current x/y position. + */ +struct uifocusitem_s { + uint8_t cols; + uint8_t rows; + uint8_t x; + uint8_t y; + uifocusitemcallback_t selected; + uifocusitemcallback_t changed; + uifocusitemcallback_t closed; +}; diff --git a/src/dusk/ui/uifullbox.c b/src/dusk/ui/uifullbox.c index 490306ef..0fdb6e74 100644 --- a/src/dusk/ui/uifullbox.c +++ b/src/dusk/ui/uifullbox.c @@ -8,6 +8,7 @@ #include "uifullbox.h" #include "assert/assert.h" #include "util/memory.h" +#include "time/time.h" #include "display/screen/screen.h" #include "display/texture/texture.h" #include "display/spritebatch/spritebatch.h" @@ -92,10 +93,30 @@ void uiFullboxTransition( fullbox->easing = easing; } +errorret_t uiFullboxUnderInit(void) { + uiFullboxInit(&UI_FULLBOX_UNDER); + errorOk(); +} + +errorret_t uiFullboxUnderUpdate(void) { + uiFullboxUpdate(&UI_FULLBOX_UNDER, TIME.delta); + errorOk(); +} + errorret_t uiFullboxUnderDraw(void) { return uiFullboxDraw(&UI_FULLBOX_UNDER); } +errorret_t uiFullboxOverInit(void) { + uiFullboxInit(&UI_FULLBOX_OVER); + errorOk(); +} + +errorret_t uiFullboxOverUpdate(void) { + uiFullboxUpdate(&UI_FULLBOX_OVER, TIME.delta); + errorOk(); +} + errorret_t uiFullboxOverDraw(void) { return uiFullboxDraw(&UI_FULLBOX_OVER); } diff --git a/src/dusk/ui/uifullbox.h b/src/dusk/ui/uifullbox.h index c1f172cc..5c1accd3 100644 --- a/src/dusk/ui/uifullbox.h +++ b/src/dusk/ui/uifullbox.h @@ -66,12 +66,44 @@ void uiFullboxTransition( easingtype_t easing ); +/** + * Init function for UI_FULLBOX_UNDER (registered in UI_ELEMENTS). + * + * @return Any error that occurs. + */ +errorret_t uiFullboxUnderInit(void); + +/** + * Update function for UI_FULLBOX_UNDER (registered in UI_ELEMENTS). + * + * @return Any error that occurs. + */ +errorret_t uiFullboxUnderUpdate(void); + /** * Draw function for UI_FULLBOX_UNDER (registered in UI_ELEMENTS). + * + * @return Any error that occurs. */ errorret_t uiFullboxUnderDraw(void); +/** + * Init function for UI_FULLBOX_OVER (registered in UI_ELEMENTS). + * + * @return Any error that occurs. + */ +errorret_t uiFullboxOverInit(void); + +/** + * Update function for UI_FULLBOX_OVER (registered in UI_ELEMENTS). + * + * @return Any error that occurs. + */ +errorret_t uiFullboxOverUpdate(void); + /** * Draw function for UI_FULLBOX_OVER (registered in UI_ELEMENTS). + * + * @return Any error that occurs. */ errorret_t uiFullboxOverDraw(void); diff --git a/src/dusk/ui/uiloading.c b/src/dusk/ui/uiloading.c index 89b04309..776869d5 100644 --- a/src/dusk/ui/uiloading.c +++ b/src/dusk/ui/uiloading.c @@ -8,6 +8,7 @@ #include "uiloading.h" #include "assert/assert.h" #include "util/memory.h" +#include "time/time.h" #include "display/text/text.h" #include "display/screen/screen.h" #include "display/spritebatch/spritebatch.h" @@ -17,7 +18,7 @@ uiloading_t UI_LOADING; -void uiLoadingInit(void) { +errorret_t uiLoadingInit(void) { memoryZero(&UI_LOADING, sizeof(uiloading_t)); eventInit( &UI_LOADING.onTransitionEnd, @@ -25,16 +26,18 @@ void uiLoadingInit(void) { UI_LOADING.onTransitionEndUsers, 4 ); + errorOk(); } -void uiLoadingUpdate(float_t delta) { - if(UI_LOADING.duration <= 0.0f || UI_LOADING.time >= UI_LOADING.duration) - return; - UI_LOADING.time += delta; - if(UI_LOADING.time >= UI_LOADING.duration) { - UI_LOADING.time = UI_LOADING.duration; - eventInvoke(&UI_LOADING.onTransitionEnd, &UI_LOADING); +errorret_t uiLoadingUpdate(void) { + if(UI_LOADING.duration > 0.0f && UI_LOADING.time < UI_LOADING.duration) { + UI_LOADING.time += TIME.delta; + if(UI_LOADING.time >= UI_LOADING.duration) { + UI_LOADING.time = UI_LOADING.duration; + eventInvoke(&UI_LOADING.onTransitionEnd, &UI_LOADING); + } } + errorOk(); } errorret_t uiLoadingDraw(void) { diff --git a/src/dusk/ui/uiloading.h b/src/dusk/ui/uiloading.h index 60bf9414..af67db6c 100644 --- a/src/dusk/ui/uiloading.h +++ b/src/dusk/ui/uiloading.h @@ -26,16 +26,18 @@ extern uiloading_t UI_LOADING; /** * Initializes the loading indicator. + * + * @return Any error that occurs. */ -void uiLoadingInit(void); +errorret_t uiLoadingInit(void); /** * Advances the loading indicator fade transition. Fires onTransitionEnd once * when the transition completes. * - * @param delta Seconds elapsed since last update. + * @return Any error that occurs. */ -void uiLoadingUpdate(float_t delta); +errorret_t uiLoadingUpdate(void); /** * Draws the loading indicator. No-op when fully transparent. diff --git a/src/dusk/ui/uitextbox.c b/src/dusk/ui/uitextbox.c index 487c1e9a..5b144ed8 100644 --- a/src/dusk/ui/uitextbox.c +++ b/src/dusk/ui/uitextbox.c @@ -271,7 +271,8 @@ errorret_t uiTextboxDraw(void) { errorOk(); } -void uiTextboxDispose(void) { +errorret_t uiTextboxDispose(void) { uiFrameDispose(&UI_TEXTBOX.frame); UI_TEXTBOX.font = NULL; + errorOk(); } diff --git a/src/dusk/ui/uitextbox.h b/src/dusk/ui/uitextbox.h index 51e73d68..60e45a90 100644 --- a/src/dusk/ui/uitextbox.h +++ b/src/dusk/ui/uitextbox.h @@ -115,5 +115,7 @@ void uiTextboxNextPage(void); /** * Disposes of UI_TEXTBOX, nulling out texture pointers. + * + * @return Any error that occurs. */ -void uiTextboxDispose(void); +errorret_t uiTextboxDispose(void); diff --git a/src/duskdolphin/input/inputdolphin.c b/src/duskdolphin/input/inputdolphin.c index 8dfc7403..c9fb4869 100644 --- a/src/duskdolphin/input/inputdolphin.c +++ b/src/duskdolphin/input/inputdolphin.c @@ -115,6 +115,7 @@ errorret_t inputInitDolphin(void) { X("b", INPUT_ACTION_CANCEL); X("z", INPUT_ACTION_CONSOLE); X("start", INPUT_ACTION_RAGEQUIT); + X("start", INPUT_ACTION_PAUSE); #elif defined(DUSK_WII) X("up", INPUT_ACTION_UP); @@ -130,6 +131,7 @@ errorret_t inputInitDolphin(void) { X("b", INPUT_ACTION_CANCEL); X("z", INPUT_ACTION_CONSOLE); X("start", INPUT_ACTION_RAGEQUIT); + X("start", INPUT_ACTION_PAUSE); // TODO: Wiimote, USB Keyboard, probably more. diff --git a/src/dusklinux/input/inputlinux.c b/src/dusklinux/input/inputlinux.c index 42ec3359..fb952efa 100644 --- a/src/dusklinux/input/inputlinux.c +++ b/src/dusklinux/input/inputlinux.c @@ -517,6 +517,7 @@ errorret_t inputInitLinux(void) { X("tab", INPUT_ACTION_CANCEL); X("q", INPUT_ACTION_CANCEL); X("escape", INPUT_ACTION_RAGEQUIT); + X("enter", INPUT_ACTION_PAUSE); X("`", INPUT_ACTION_CONSOLE); #endif @@ -528,6 +529,7 @@ errorret_t inputInitLinux(void) { X("gamepad_a", INPUT_ACTION_ACCEPT); X("gamepad_b", INPUT_ACTION_CANCEL); X("gamepad_back", INPUT_ACTION_RAGEQUIT); + X("gamepad_start", INPUT_ACTION_PAUSE); X("gamepad_lstick_up", INPUT_ACTION_UP); X("gamepad_lstick_down", INPUT_ACTION_DOWN); X("gamepad_lstick_left", INPUT_ACTION_LEFT); diff --git a/src/duskpsp/input/inputpsp.c b/src/duskpsp/input/inputpsp.c index 313ceb95..6bc7ef62 100644 --- a/src/duskpsp/input/inputpsp.c +++ b/src/duskpsp/input/inputpsp.c @@ -83,6 +83,7 @@ errorret_t inputInitPSP(void) { X("cancel", INPUT_ACTION_CANCEL); X("triangle", INPUT_ACTION_CONSOLE); X("select", INPUT_ACTION_RAGEQUIT); + X("start", INPUT_ACTION_PAUSE); X("lstick_up", INPUT_ACTION_UP); X("lstick_down", INPUT_ACTION_DOWN); X("lstick_left", INPUT_ACTION_LEFT); -- 2.52.0 From a9e33660cb1598e88e300e0c5d804dc02ed0782a Mon Sep 17 00:00:00 2001 From: Dominic Masters Date: Thu, 25 Jun 2026 15:33:17 -0500 Subject: [PATCH 04/16] UI Frame --- src/dusk/ui/CMakeLists.txt | 10 +- src/dusk/ui/{uifocus => focus}/CMakeLists.txt | 0 src/dusk/ui/{uifocus => focus}/uifocus.c | 0 src/dusk/ui/{uifocus => focus}/uifocus.h | 0 src/dusk/ui/{uifocus => focus}/uifocusitem.h | 0 src/dusk/ui/frame/CMakeLists.txt | 10 + src/dusk/ui/{ => frame}/uiframe.c | 86 +++- src/dusk/ui/{ => frame}/uiframe.h | 32 +- src/dusk/ui/frame/uisettings.c | 34 ++ src/dusk/ui/frame/uisettings.h | 43 ++ src/dusk/ui/overlay/CMakeLists.txt | 11 + src/dusk/ui/{ => overlay}/uicrop.c | 0 src/dusk/ui/{ => overlay}/uicrop.h | 0 src/dusk/ui/{ => overlay}/uifullbox.c | 0 src/dusk/ui/{ => overlay}/uifullbox.h | 0 src/dusk/ui/{ => overlay}/uiloading.c | 2 +- src/dusk/ui/{ => overlay}/uiloading.h | 0 src/dusk/ui/ui.c | 10 +- src/dusk/ui/uielement.c | 46 +- src/dusk/ui/uielement.h | 17 +- src/dusk/ui/uitextbox.c | 460 +++++++++--------- src/dusk/ui/uitextbox.h | 200 ++++---- 22 files changed, 563 insertions(+), 398 deletions(-) rename src/dusk/ui/{uifocus => focus}/CMakeLists.txt (100%) rename src/dusk/ui/{uifocus => focus}/uifocus.c (100%) rename src/dusk/ui/{uifocus => focus}/uifocus.h (100%) rename src/dusk/ui/{uifocus => focus}/uifocusitem.h (100%) create mode 100644 src/dusk/ui/frame/CMakeLists.txt rename src/dusk/ui/{ => frame}/uiframe.c (63%) rename src/dusk/ui/{ => frame}/uiframe.h (58%) create mode 100644 src/dusk/ui/frame/uisettings.c create mode 100644 src/dusk/ui/frame/uisettings.h create mode 100644 src/dusk/ui/overlay/CMakeLists.txt rename src/dusk/ui/{ => overlay}/uicrop.c (100%) rename src/dusk/ui/{ => overlay}/uicrop.h (100%) rename src/dusk/ui/{ => overlay}/uifullbox.c (100%) rename src/dusk/ui/{ => overlay}/uifullbox.h (100%) rename src/dusk/ui/{ => overlay}/uiloading.c (99%) rename src/dusk/ui/{ => overlay}/uiloading.h (100%) diff --git a/src/dusk/ui/CMakeLists.txt b/src/dusk/ui/CMakeLists.txt index 9fe2e9c8..524c4a35 100644 --- a/src/dusk/ui/CMakeLists.txt +++ b/src/dusk/ui/CMakeLists.txt @@ -4,16 +4,14 @@ # https://opensource.org/licenses/MIT add_subdirectory(debug) -add_subdirectory(uifocus) +add_subdirectory(frame) +add_subdirectory(focus) +add_subdirectory(overlay) # Sources target_sources(${DUSK_LIBRARY_TARGET_NAME} PUBLIC ui.c - uicrop.c uielement.c - uiframe.c - uifullbox.c - uiloading.c - uitextbox.c + # uitextbox.c ) \ No newline at end of file diff --git a/src/dusk/ui/uifocus/CMakeLists.txt b/src/dusk/ui/focus/CMakeLists.txt similarity index 100% rename from src/dusk/ui/uifocus/CMakeLists.txt rename to src/dusk/ui/focus/CMakeLists.txt diff --git a/src/dusk/ui/uifocus/uifocus.c b/src/dusk/ui/focus/uifocus.c similarity index 100% rename from src/dusk/ui/uifocus/uifocus.c rename to src/dusk/ui/focus/uifocus.c diff --git a/src/dusk/ui/uifocus/uifocus.h b/src/dusk/ui/focus/uifocus.h similarity index 100% rename from src/dusk/ui/uifocus/uifocus.h rename to src/dusk/ui/focus/uifocus.h diff --git a/src/dusk/ui/uifocus/uifocusitem.h b/src/dusk/ui/focus/uifocusitem.h similarity index 100% rename from src/dusk/ui/uifocus/uifocusitem.h rename to src/dusk/ui/focus/uifocusitem.h diff --git a/src/dusk/ui/frame/CMakeLists.txt b/src/dusk/ui/frame/CMakeLists.txt new file mode 100644 index 00000000..98b01056 --- /dev/null +++ b/src/dusk/ui/frame/CMakeLists.txt @@ -0,0 +1,10 @@ +# Copyright (c) 2026 Dominic Masters +# +# This software is released under the MIT License. +# https://opensource.org/licenses/MIT + +target_sources(${DUSK_LIBRARY_TARGET_NAME} + PUBLIC + uiframe.c + uisettings.c +) diff --git a/src/dusk/ui/uiframe.c b/src/dusk/ui/frame/uiframe.c similarity index 63% rename from src/dusk/ui/uiframe.c rename to src/dusk/ui/frame/uiframe.c index 222379be..8608e5db 100644 --- a/src/dusk/ui/uiframe.c +++ b/src/dusk/ui/frame/uiframe.c @@ -1,43 +1,76 @@ -// Copyright (c) 2026 Dominic Masters -// -// This software is released under the MIT License. -// https://opensource.org/licenses/MIT +/** + * Copyright (c) 2026 Dominic Masters + * + * This software is released under the MIT License. + * https://opensource.org/licenses/MIT + */ #include "uiframe.h" #include "assert/assert.h" #include "util/memory.h" +#include "display/texture/texture.h" #include "display/spritebatch/spritebatch.h" #include "display/shader/shaderunlit.h" #include "display/color.h" -errorret_t uiFrameInit(uiframe_t *frame) { - assertNotNull(frame, "frame must not be NULL"); - memoryZero(frame, sizeof(uiframe_t)); +uiframe_t UI_FRAME; + +errorret_t uiFrameInit(void) { + memoryZero(&UI_FRAME, sizeof(uiframe_t)); + + // Init texture. + color_t border = color4b(0, 100, 220, 200); + color_t center = color4b(0, 100, 220, 50); + + for(uint8_t y = 0; y < UIFRAME_DUMMY_HEIGHT; y++) { + for(uint8_t x = 0; x < UIFRAME_DUMMY_WIDTH; x++) { + color_t c; + if(x >= 12 || y >= 12) { + c = COLOR_TRANSPARENT; + } else if(y < 4 || y >= 8 || x < 4 || x >= 8) { + c = border; + } else { + c = center; + } + UI_FRAME.pixels[y * UIFRAME_DUMMY_WIDTH + x] = c; + } + } + + errorChain(textureInit( + &UI_FRAME.texture, UIFRAME_DUMMY_WIDTH, UIFRAME_DUMMY_HEIGHT, + TEXTURE_FORMAT_RGBA, + (texturedata_t){ .rgbaColors = UI_FRAME.pixels } + )); + + UI_FRAME.tileset.tileWidth = 4; + UI_FRAME.tileset.tileHeight = 4; + UI_FRAME.tileset.columns = 3; + UI_FRAME.tileset.rows = 3; + UI_FRAME.tileset.tileCount = 9; + UI_FRAME.tileset.uv[0] = 4.0f / UIFRAME_DUMMY_WIDTH; + UI_FRAME.tileset.uv[1] = 4.0f / UIFRAME_DUMMY_HEIGHT; + errorOk(); } errorret_t uiFrameDraw( - const uiframe_t *frame, const float_t x, const float_t y, const float_t width, const float_t height ) { - assertNotNull(frame, "frame must not be NULL"); - assertNotNull(frame->texture, "frame texture must not be NULL"); - shadermaterial_t material = { .unlit = { .color = COLOR_WHITE, - .texture = frame->texture + .texture = &UI_FRAME.texture } }; spritebatchsprite_t sprites[9]; - float_t tileW = (float_t)frame->tileset.tileWidth; - float_t tileH = (float_t)frame->tileset.tileHeight; + float_t tileW = (float_t)UI_FRAME.tileset.tileWidth; + float_t tileH = (float_t)UI_FRAME.tileset.tileHeight; vec4 uv; - tilesetPositionGetUV(&frame->tileset, 0, 0, uv); + tilesetPositionGetUV(&UI_FRAME.tileset, 0, 0, uv); sprites[0].min[0] = x; sprites[0].min[1] = y; sprites[0].min[2] = 0.0f; @@ -49,19 +82,19 @@ errorret_t uiFrameDraw( sprites[0].uvMax[0] = uv[2]; sprites[0].uvMax[1] = uv[3]; - tilesetPositionGetUV(&frame->tileset, 1, 0, uv); + tilesetPositionGetUV(&UI_FRAME.tileset, 1, 0, uv); sprites[1].min[0] = x + tileW; sprites[1].min[1] = y; sprites[1].min[2] = 0.0f; sprites[1].max[0] = x + width - tileW; sprites[1].max[1] = y + tileH; sprites[1].max[2] = 0.0f; - sprites[1].uvMin[0] = uv[0]; + sprites[1].uvMin[0] = uv[0]; sprites[1].uvMin[1] = uv[1]; sprites[1].uvMax[0] = uv[2]; sprites[1].uvMax[1] = uv[3]; - tilesetPositionGetUV(&frame->tileset, 2, 0, uv); + tilesetPositionGetUV(&UI_FRAME.tileset, 2, 0, uv); sprites[2].min[0] = x + width - tileW; sprites[2].min[1] = y; sprites[2].min[2] = 0.0f; @@ -73,7 +106,7 @@ errorret_t uiFrameDraw( sprites[2].uvMax[0] = uv[2]; sprites[2].uvMax[1] = uv[3]; - tilesetPositionGetUV(&frame->tileset, 0, 1, uv); + tilesetPositionGetUV(&UI_FRAME.tileset, 0, 1, uv); sprites[3].min[0] = x; sprites[3].min[1] = y + tileH; sprites[3].min[2] = 0.0f; @@ -85,7 +118,7 @@ errorret_t uiFrameDraw( sprites[3].uvMax[0] = uv[2]; sprites[3].uvMax[1] = uv[3]; - tilesetPositionGetUV(&frame->tileset, 1, 1, uv); + tilesetPositionGetUV(&UI_FRAME.tileset, 1, 1, uv); sprites[4].min[0] = x + tileW; sprites[4].min[1] = y + tileH; sprites[4].min[2] = 0.0f; @@ -97,7 +130,7 @@ errorret_t uiFrameDraw( sprites[4].uvMax[0] = uv[2]; sprites[4].uvMax[1] = uv[3]; - tilesetPositionGetUV(&frame->tileset, 2, 1, uv); + tilesetPositionGetUV(&UI_FRAME.tileset, 2, 1, uv); sprites[5].min[0] = x + width - tileW; sprites[5].min[1] = y + tileH; sprites[5].min[2] = 0.0f; @@ -109,7 +142,7 @@ errorret_t uiFrameDraw( sprites[5].uvMax[0] = uv[2]; sprites[5].uvMax[1] = uv[3]; - tilesetPositionGetUV(&frame->tileset, 0, 2, uv); + tilesetPositionGetUV(&UI_FRAME.tileset, 0, 2, uv); sprites[6].min[0] = x; sprites[6].min[1] = y + height - tileH; sprites[6].min[2] = 0.0f; @@ -121,7 +154,7 @@ errorret_t uiFrameDraw( sprites[6].uvMax[0] = uv[2]; sprites[6].uvMax[1] = uv[3]; - tilesetPositionGetUV(&frame->tileset, 1, 2, uv); + tilesetPositionGetUV(&UI_FRAME.tileset, 1, 2, uv); sprites[7].min[0] = x + tileW; sprites[7].min[1] = y + height - tileH; sprites[7].min[2] = 0.0f; @@ -133,7 +166,7 @@ errorret_t uiFrameDraw( sprites[7].uvMax[0] = uv[2]; sprites[7].uvMax[1] = uv[3]; - tilesetPositionGetUV(&frame->tileset, 2, 2, uv); + tilesetPositionGetUV(&UI_FRAME.tileset, 2, 2, uv); sprites[8].min[0] = x + width - tileW; sprites[8].min[1] = y + height - tileH; sprites[8].min[2] = 0.0f; @@ -148,7 +181,6 @@ errorret_t uiFrameDraw( return spriteBatchBuffer(sprites, 9, &SHADER_UNLIT, material); } -void uiFrameDispose(uiframe_t *frame) { - assertNotNull(frame, "frame must not be NULL"); - frame->texture = NULL; +errorret_t uiFrameDispose(void) { + return textureDispose(&UI_FRAME.texture); } diff --git a/src/dusk/ui/uiframe.h b/src/dusk/ui/frame/uiframe.h similarity index 58% rename from src/dusk/ui/uiframe.h rename to src/dusk/ui/frame/uiframe.h index c6c4b3be..64c45274 100644 --- a/src/dusk/ui/uiframe.h +++ b/src/dusk/ui/frame/uiframe.h @@ -10,24 +10,29 @@ #include "display/texture/texture.h" #include "display/texture/tileset.h" +#define UIFRAME_DUMMY_WIDTH 16 +#define UIFRAME_DUMMY_HEIGHT 16 + typedef struct { tileset_t tileset; - texture_t *texture; + texture_t texture; + color_t pixels[UIFRAME_DUMMY_WIDTH * UIFRAME_DUMMY_HEIGHT]; } uiframe_t; -/** - * Initializes a UI frame element. - * - * @param frame The frame to initialize. - * @return Any error that occurs. - */ -errorret_t uiFrameInit(uiframe_t *frame); +extern uiframe_t UI_FRAME; /** - * Draws a UI frame using 9-slice rendering from a 3x3 tileset. + * Initializes the global UI_FRAME: builds the dummy texture and + * configures the tileset. Call once after displayInit(). + * + * @return Any error that occurs. + */ +errorret_t uiFrameInit(void); + +/** + * Draws UI_FRAME using 9-slice rendering from its 3x3 tileset. * Pushes quads to the sprite batch without flushing. * - * @param frame The frame to draw. * @param x Screen x position. * @param y Screen y position. * @param width Total width of the frame. @@ -35,7 +40,6 @@ errorret_t uiFrameInit(uiframe_t *frame); * @return Any error that occurs. */ errorret_t uiFrameDraw( - const uiframe_t *frame, const float_t x, const float_t y, const float_t width, @@ -43,8 +47,8 @@ errorret_t uiFrameDraw( ); /** - * Disposes of a UI frame element. Does not dispose the texture. + * Disposes of the global UI_FRAME, releasing its GPU texture. * - * @param frame The frame to dispose. + * @return Any error that occurs. */ -void uiFrameDispose(uiframe_t *frame); +errorret_t uiFrameDispose(void); diff --git a/src/dusk/ui/frame/uisettings.c b/src/dusk/ui/frame/uisettings.c new file mode 100644 index 00000000..7408e79a --- /dev/null +++ b/src/dusk/ui/frame/uisettings.c @@ -0,0 +1,34 @@ +/** + * Copyright (c) 2026 Dominic Masters + * + * This software is released under the MIT License. + * https://opensource.org/licenses/MIT + */ + +#include "uisettings.h" +#include "ui/frame/uiframe.h" +#include "util/memory.h" + +uisettings_t UI_SETTINGS; + +errorret_t uiSettingsInit(void) { + memoryZero(&UI_SETTINGS, sizeof(uisettings_t)); + errorOk(); +} + +errorret_t uiSettingsUpdate(void) { + // if(!UI_SETTINGS.visible) errorOk(); + errorOk(); +} + +errorret_t uiSettingsDraw(void) { + // if(!UI_SETTINGS.visible) errorOk(); + errorChain(uiFrameDraw( + 100.0f, 100.0f, 1.0f, 1.0f + )); + errorOk(); +} + +errorret_t uiSettingsDispose(void) { + errorOk(); +} diff --git a/src/dusk/ui/frame/uisettings.h b/src/dusk/ui/frame/uisettings.h new file mode 100644 index 00000000..bc7f2d24 --- /dev/null +++ b/src/dusk/ui/frame/uisettings.h @@ -0,0 +1,43 @@ +/** + * Copyright (c) 2026 Dominic Masters + * + * This software is released under the MIT License. + * https://opensource.org/licenses/MIT + */ + +#pragma once +#include "error/error.h" + +typedef struct { + bool_t visible; +} uisettings_t; + +extern uisettings_t UI_SETTINGS; + +/** + * Initializes the settings panel. + * + * @return Any error that occurs. + */ +errorret_t uiSettingsInit(void); + +/** + * Updates the settings panel. Handles input when visible. + * + * @return Any error that occurs. + */ +errorret_t uiSettingsUpdate(void); + +/** + * Draws the settings panel. No-op when not visible. + * + * @return Any error that occurs. + */ +errorret_t uiSettingsDraw(void); + +/** + * Disposes of the settings panel. + * + * @return Any error that occurs. + */ +errorret_t uiSettingsDispose(void); diff --git a/src/dusk/ui/overlay/CMakeLists.txt b/src/dusk/ui/overlay/CMakeLists.txt new file mode 100644 index 00000000..d6c051b3 --- /dev/null +++ b/src/dusk/ui/overlay/CMakeLists.txt @@ -0,0 +1,11 @@ +# Copyright (c) 2026 Dominic Masters +# +# This software is released under the MIT License. +# https://opensource.org/licenses/MIT + +target_sources(${DUSK_LIBRARY_TARGET_NAME} + PUBLIC + uicrop.c + uifullbox.c + uiloading.c +) diff --git a/src/dusk/ui/uicrop.c b/src/dusk/ui/overlay/uicrop.c similarity index 100% rename from src/dusk/ui/uicrop.c rename to src/dusk/ui/overlay/uicrop.c diff --git a/src/dusk/ui/uicrop.h b/src/dusk/ui/overlay/uicrop.h similarity index 100% rename from src/dusk/ui/uicrop.h rename to src/dusk/ui/overlay/uicrop.h diff --git a/src/dusk/ui/uifullbox.c b/src/dusk/ui/overlay/uifullbox.c similarity index 100% rename from src/dusk/ui/uifullbox.c rename to src/dusk/ui/overlay/uifullbox.c diff --git a/src/dusk/ui/uifullbox.h b/src/dusk/ui/overlay/uifullbox.h similarity index 100% rename from src/dusk/ui/uifullbox.h rename to src/dusk/ui/overlay/uifullbox.h diff --git a/src/dusk/ui/uiloading.c b/src/dusk/ui/overlay/uiloading.c similarity index 99% rename from src/dusk/ui/uiloading.c rename to src/dusk/ui/overlay/uiloading.c index 776869d5..4dc86e3b 100644 --- a/src/dusk/ui/uiloading.c +++ b/src/dusk/ui/overlay/uiloading.c @@ -12,7 +12,7 @@ #include "display/text/text.h" #include "display/screen/screen.h" #include "display/spritebatch/spritebatch.h" -#include "ui/uifullbox.h" +#include "uifullbox.h" #define UI_LOADING_TEXT "loading" diff --git a/src/dusk/ui/uiloading.h b/src/dusk/ui/overlay/uiloading.h similarity index 100% rename from src/dusk/ui/uiloading.h rename to src/dusk/ui/overlay/uiloading.h diff --git a/src/dusk/ui/ui.c b/src/dusk/ui/ui.c index f069dfd5..566672fc 100644 --- a/src/dusk/ui/ui.c +++ b/src/dusk/ui/ui.c @@ -11,7 +11,7 @@ #include "display/spritebatch/spritebatch.h" #include "display/screen/screen.h" #include "ui/uielement.h" -#include "ui/uifocus/uifocus.h" +#include "ui/focus/uifocus.h" #include "log/log.h" ui_t UI; @@ -21,7 +21,7 @@ errorret_t uiInit(void) { uiFocusInit(); uielement_t *element = &UI_ELEMENTS[0]; - while(element->draw != NULL) { + while(!uiElementIsNull(element)) { errorChain(uiElementInit(element)); element++; } @@ -33,7 +33,7 @@ errorret_t uiUpdate(void) { uiFocusUpdate(); uielement_t *element = &UI_ELEMENTS[0]; - while(element->draw != NULL) { + while(!uiElementIsNull(element)) { errorChain(uiElementUpdate(element)); element++; } @@ -43,7 +43,7 @@ errorret_t uiUpdate(void) { errorret_t uiRender(void) { const uielement_t *element = &UI_ELEMENTS[0]; - while(element->draw != NULL) { + while(!uiElementIsNull(element)) { errorChain(uiElementDraw(element)); if(SPRITEBATCH.spriteCount > 0) { @@ -58,7 +58,7 @@ errorret_t uiRender(void) { errorret_t uiDispose(void) { uielement_t *element = &UI_ELEMENTS[0]; - while(element->draw != NULL) { + while(!uiElementIsNull(element)) { errorChain(uiElementDispose(element)); element++; } diff --git a/src/dusk/ui/uielement.c b/src/dusk/ui/uielement.c index ce88bc61..a5dd6e31 100644 --- a/src/dusk/ui/uielement.c +++ b/src/dusk/ui/uielement.c @@ -7,16 +7,24 @@ #include "uielement.h" #include "assert/assert.h" +#include "ui/frame/uiframe.h" #include "ui/debug/uifps.h" #include "engine/engine.h" #include "ui/uitextbox.h" -#include "ui/uifullbox.h" -#include "ui/uiloading.h" +#include "ui/overlay/uifullbox.h" +#include "ui/overlay/uiloading.h" #include "ui/debug/uiplayerpos.h" -#include "ui/uicrop.h" +#include "ui/overlay/uicrop.h" #include "ui/debug/uiconsole.h" +#include "ui/frame/uiframe.h" +#include "ui/frame/uisettings.h" uielement_t UI_ELEMENTS[] = { + { + .init = uiFrameInit, + .dispose = uiFrameDispose + }, + // Fullbox under: above scene, below system UI. { @@ -25,14 +33,21 @@ uielement_t UI_ELEMENTS[] = { .draw = uiFullboxUnderDraw }, + { + .init = uiSettingsInit, + .update = uiSettingsUpdate, + .draw = uiSettingsDraw, + .dispose = uiSettingsDispose + }, + // { .type = UI_ELEMENT_TYPE_SCRIPT, .script = { .script = "ui/test.js" } }, - { - .init = uiTextboxInit, - .update = uiTextboxUpdate, - .draw = uiTextboxDraw, - .dispose = uiTextboxDispose - }, + // { + // .init = uiTextboxInit, + // .update = uiTextboxUpdate, + // .draw = uiTextboxDraw, + // .dispose = uiTextboxDispose + // }, // Fullbox over: above absolutely everything. { @@ -60,8 +75,16 @@ uielement_t UI_ELEMENTS[] = { { 0 } // Null terminator }; +bool_t uiElementIsNull(const uielement_t *element) { + return element->init == NULL && + element->update == NULL && + element->draw == NULL && + element->dispose == NULL; +} + errorret_t uiElementInit(uielement_t *element) { assertNotNull(element, "element must not be NULL"); + errorChain(uiFrameInit()); if(element->init != NULL) errorChain(element->init()); errorOk(); } @@ -74,12 +97,13 @@ errorret_t uiElementUpdate(uielement_t *element) { errorret_t uiElementDraw(const uielement_t *element) { assertNotNull(element, "element must not be NULL"); - assertNotNull(element->draw, "element draw callback must not be NULL"); - return element->draw(); + if(element->draw != NULL) errorChain(element->draw()); + errorOk(); } errorret_t uiElementDispose(uielement_t *element) { assertNotNull(element, "element must not be NULL"); if(element->dispose != NULL) errorChain(element->dispose()); + errorChain(uiFrameDispose()); errorOk(); } \ No newline at end of file diff --git a/src/dusk/ui/uielement.h b/src/dusk/ui/uielement.h index 43322116..49db9d60 100644 --- a/src/dusk/ui/uielement.h +++ b/src/dusk/ui/uielement.h @@ -1,6 +1,6 @@ /** * Copyright (c) 2026 Dominic Masters - * + * * This software is released under the MIT License. * https://opensource.org/licenses/MIT */ @@ -18,7 +18,16 @@ typedef struct { extern uielement_t UI_ELEMENTS[]; /** - * Initializes a UI element, calling its init callback if set. + * Returns true when all four callbacks on the element are NULL, + * which marks the end of the UI_ELEMENTS array. + * + * @param element The element to test. + * @returns True if the element is the null terminator. + */ +bool_t uiElementIsNull(const uielement_t *element); + +/** + * Initializes a UI element, invoking its init callback if set. * * @param element The element to initialize. * @return Any error that occurs. @@ -42,9 +51,9 @@ errorret_t uiElementUpdate(uielement_t *element); errorret_t uiElementDraw(const uielement_t *element); /** - * Disposes of a UI element, calling its dispose callback if set. + * Disposes of a UI element, invoking its dispose callback if set. * * @param element The element to dispose. * @return Any error that occurs. */ -errorret_t uiElementDispose(uielement_t *element); \ No newline at end of file +errorret_t uiElementDispose(uielement_t *element); diff --git a/src/dusk/ui/uitextbox.c b/src/dusk/ui/uitextbox.c index 5b144ed8..fc1231f2 100644 --- a/src/dusk/ui/uitextbox.c +++ b/src/dusk/ui/uitextbox.c @@ -1,278 +1,278 @@ -// Copyright (c) 2026 Dominic Masters -// -// This software is released under the MIT License. -// https://opensource.org/licenses/MIT +// // Copyright (c) 2026 Dominic Masters +// // +// // This software is released under the MIT License. +// // https://opensource.org/licenses/MIT -#include "uitextbox.h" -#include "assert/assert.h" -#include "util/memory.h" -#include "util/string.h" -#include "time/time.h" -#include "input/input.h" -#include "event/event.h" -#include "display/screen/screen.h" -#include "display/texture/texture.h" -#include "display/text/text.h" -#include "display/spritebatch/spritebatch.h" -#include "display/shader/shaderunlit.h" +// #include "uitextbox.h" +// #include "assert/assert.h" +// #include "util/memory.h" +// #include "util/string.h" +// #include "time/time.h" +// #include "input/input.h" +// #include "event/event.h" +// #include "display/screen/screen.h" +// #include "display/texture/texture.h" +// #include "display/text/text.h" +// #include "display/spritebatch/spritebatch.h" +// #include "display/shader/shaderunlit.h" -uitextbox_t UI_TEXTBOX; +// uitextbox_t UI_TEXTBOX; -errorret_t uiTextboxInit(void) { - memoryZero(&UI_TEXTBOX, sizeof(uitextbox_t)); - UI_TEXTBOX.textColor = COLOR_WHITE; - UI_TEXTBOX.advanceAction = INPUT_ACTION_ACCEPT; +// errorret_t uiTextboxInit(void) { +// memoryZero(&UI_TEXTBOX, sizeof(uitextbox_t)); +// UI_TEXTBOX.textColor = COLOR_WHITE; +// UI_TEXTBOX.advanceAction = INPUT_ACTION_ACCEPT; - UI_TEXTBOX.font = &FONT_DEFAULT; +// UI_TEXTBOX.font = &FONT_DEFAULT; - float_t fontW = (float_t)FONT_DEFAULT.tileset->tileWidth; - float_t fontH = (float_t)FONT_DEFAULT.tileset->tileHeight; - float_t tbHeight = ( - (float_t)UI_TEXTBOX_LINES_PER_PAGE_MAX * fontH + - (float_t)(UI_TEXTBOX_LINES_PER_PAGE_MAX - 1) * UI_TEXTBOX_LINE_SPACING + - 2.0f * fontH - ); - UI_TEXTBOX.x = (float_t)SCREEN.scanX + 10.0f; - UI_TEXTBOX.y = (float_t)(SCREEN.scanY + SCREEN.scanHeight) - - tbHeight - 10.0f; - UI_TEXTBOX.width = (float_t)SCREEN.scanWidth - 20.0f; - UI_TEXTBOX.height = tbHeight; +// float_t fontW = (float_t)FONT_DEFAULT.tileset->tileWidth; +// float_t fontH = (float_t)FONT_DEFAULT.tileset->tileHeight; +// float_t tbHeight = ( +// (float_t)UI_TEXTBOX_LINES_PER_PAGE_MAX * fontH + +// (float_t)(UI_TEXTBOX_LINES_PER_PAGE_MAX - 1) * UI_TEXTBOX_LINE_SPACING + +// 2.0f * fontH +// ); +// UI_TEXTBOX.x = (float_t)SCREEN.scanX + 10.0f; +// UI_TEXTBOX.y = (float_t)(SCREEN.scanY + SCREEN.scanHeight) - +// tbHeight - 10.0f; +// UI_TEXTBOX.width = (float_t)SCREEN.scanWidth - 20.0f; +// UI_TEXTBOX.height = tbHeight; - UI_TEXTBOX.frame.tileset.columns = 3; - UI_TEXTBOX.frame.tileset.rows = 3; - UI_TEXTBOX.frame.tileset.tileCount = 9; - UI_TEXTBOX.frame.tileset.tileWidth = FONT_DEFAULT.tileset->tileWidth; - UI_TEXTBOX.frame.tileset.tileHeight = FONT_DEFAULT.tileset->tileHeight; - UI_TEXTBOX.frame.tileset.uv[0] = 1.0f / 3.0f; - UI_TEXTBOX.frame.tileset.uv[1] = 1.0f / 3.0f; - UI_TEXTBOX.frame.texture = &TEXTURE_WHITE; +// UI_TEXTBOX.frame.tileset.columns = 3; +// UI_TEXTBOX.frame.tileset.rows = 3; +// UI_TEXTBOX.frame.tileset.tileCount = 9; +// UI_TEXTBOX.frame.tileset.tileWidth = FONT_DEFAULT.tileset->tileWidth; +// UI_TEXTBOX.frame.tileset.tileHeight = FONT_DEFAULT.tileset->tileHeight; +// UI_TEXTBOX.frame.tileset.uv[0] = 1.0f / 3.0f; +// UI_TEXTBOX.frame.tileset.uv[1] = 1.0f / 3.0f; +// UI_TEXTBOX.frame.texture = &TEXTURE_WHITE; - eventInit( - &UI_TEXTBOX.onPageComplete, - UI_TEXTBOX.onPageCompleteCallbacks, - UI_TEXTBOX.onPageCompleteUsers, - 4 - ); - eventInit( - &UI_TEXTBOX.onLastPage, - UI_TEXTBOX.onLastPageCallbacks, - UI_TEXTBOX.onLastPageUsers, - 4 - ); +// eventInit( +// &UI_TEXTBOX.onPageComplete, +// UI_TEXTBOX.onPageCompleteCallbacks, +// UI_TEXTBOX.onPageCompleteUsers, +// 4 +// ); +// eventInit( +// &UI_TEXTBOX.onLastPage, +// UI_TEXTBOX.onLastPageCallbacks, +// UI_TEXTBOX.onLastPageUsers, +// 4 +// ); - errorOk(); -} +// errorOk(); +// } -void uiTextboxBuildLayout(void) { - UI_TEXTBOX.lineCount = 0; - UI_TEXTBOX.pageCount = 1; +// void uiTextboxBuildLayout(void) { +// UI_TEXTBOX.lineCount = 0; +// UI_TEXTBOX.pageCount = 1; - float_t frameTileW = (float_t)UI_TEXTBOX.frame.tileset.tileWidth; - float_t frameTileH = (float_t)UI_TEXTBOX.frame.tileset.tileHeight; - float_t fontW = (float_t)UI_TEXTBOX.font->tileset->tileWidth; - float_t fontH = (float_t)UI_TEXTBOX.font->tileset->tileHeight; +// float_t frameTileW = (float_t)UI_TEXTBOX.frame.tileset.tileWidth; +// float_t frameTileH = (float_t)UI_TEXTBOX.frame.tileset.tileHeight; +// float_t fontW = (float_t)UI_TEXTBOX.font->tileset->tileWidth; +// float_t fontH = (float_t)UI_TEXTBOX.font->tileset->tileHeight; - if(fontW <= 0.0f || fontH <= 0.0f) return; +// if(fontW <= 0.0f || fontH <= 0.0f) return; - float_t contentW = UI_TEXTBOX.width - 2.0f * frameTileW; - float_t contentH = UI_TEXTBOX.height - 2.0f * frameTileH; +// float_t contentW = UI_TEXTBOX.width - 2.0f * frameTileW; +// float_t contentH = UI_TEXTBOX.height - 2.0f * frameTileH; - UI_TEXTBOX.charsPerLine = (int32_t)(contentW / fontW); - UI_TEXTBOX.linesPerPage = UI_TEXTBOX_LINES_PER_PAGE_MAX; +// UI_TEXTBOX.charsPerLine = (int32_t)(contentW / fontW); +// UI_TEXTBOX.linesPerPage = UI_TEXTBOX_LINES_PER_PAGE_MAX; - if(UI_TEXTBOX.charsPerLine <= 0 || UI_TEXTBOX.linesPerPage <= 0) return; - if(UI_TEXTBOX.text[0] == '\0') return; +// if(UI_TEXTBOX.charsPerLine <= 0 || UI_TEXTBOX.linesPerPage <= 0) return; +// if(UI_TEXTBOX.text[0] == '\0') return; - char_t *src = UI_TEXTBOX.text; - int32_t i = 0; +// char_t *src = UI_TEXTBOX.text; +// int32_t i = 0; - while(src[i] != '\0' && UI_TEXTBOX.lineCount < UI_TEXTBOX_LINES_MAX) { - if(src[i] == '\t') { - i++; - int32_t rem = UI_TEXTBOX.lineCount % UI_TEXTBOX.linesPerPage; - int32_t pad = rem > 0 ? UI_TEXTBOX.linesPerPage - rem : 0; - while(pad > 0 && UI_TEXTBOX.lineCount < UI_TEXTBOX_LINES_MAX) { - UI_TEXTBOX.lines[UI_TEXTBOX.lineCount].start = i; - UI_TEXTBOX.lines[UI_TEXTBOX.lineCount].count = 0; - UI_TEXTBOX.lineCount++; - pad--; - } - continue; - } +// while(src[i] != '\0' && UI_TEXTBOX.lineCount < UI_TEXTBOX_LINES_MAX) { +// if(src[i] == '\t') { +// i++; +// int32_t rem = UI_TEXTBOX.lineCount % UI_TEXTBOX.linesPerPage; +// int32_t pad = rem > 0 ? UI_TEXTBOX.linesPerPage - rem : 0; +// while(pad > 0 && UI_TEXTBOX.lineCount < UI_TEXTBOX_LINES_MAX) { +// UI_TEXTBOX.lines[UI_TEXTBOX.lineCount].start = i; +// UI_TEXTBOX.lines[UI_TEXTBOX.lineCount].count = 0; +// UI_TEXTBOX.lineCount++; +// pad--; +// } +// continue; +// } - int32_t lineStart = i; - int32_t lineWidth = 0; +// int32_t lineStart = i; +// int32_t lineWidth = 0; - while(src[i] != '\0') { - char_t c = src[i]; +// while(src[i] != '\0') { +// char_t c = src[i]; - if(c == '\n') { - i++; - break; - } +// if(c == '\n') { +// i++; +// break; +// } - if(c == '\t') break; +// if(c == '\t') break; - if(c == ' ') { - int32_t wordLen = 0; - int32_t j = i + 1; - while( - src[j] != ' ' && src[j] != '\n' && - src[j] != '\t' && src[j] != '\0' - ) { - wordLen++; - j++; - } +// if(c == ' ') { +// int32_t wordLen = 0; +// int32_t j = i + 1; +// while( +// src[j] != ' ' && src[j] != '\n' && +// src[j] != '\t' && src[j] != '\0' +// ) { +// wordLen++; +// j++; +// } - if( - lineWidth > 0 && - lineWidth + 1 + wordLen > UI_TEXTBOX.charsPerLine - ) { - i++; - break; - } +// if( +// lineWidth > 0 && +// lineWidth + 1 + wordLen > UI_TEXTBOX.charsPerLine +// ) { +// i++; +// break; +// } - lineWidth++; - i++; - } else { - if(lineWidth >= UI_TEXTBOX.charsPerLine) break; - lineWidth++; - i++; - } - } +// lineWidth++; +// i++; +// } else { +// if(lineWidth >= UI_TEXTBOX.charsPerLine) break; +// lineWidth++; +// i++; +// } +// } - UI_TEXTBOX.lines[UI_TEXTBOX.lineCount].start = lineStart; - UI_TEXTBOX.lines[UI_TEXTBOX.lineCount].count = lineWidth; - UI_TEXTBOX.lineCount++; - } +// UI_TEXTBOX.lines[UI_TEXTBOX.lineCount].start = lineStart; +// UI_TEXTBOX.lines[UI_TEXTBOX.lineCount].count = lineWidth; +// UI_TEXTBOX.lineCount++; +// } - if(UI_TEXTBOX.lineCount == 0) { - UI_TEXTBOX.pageCount = 1; - } else { - int32_t div = UI_TEXTBOX.lineCount + UI_TEXTBOX.linesPerPage - 1; - UI_TEXTBOX.pageCount = div / UI_TEXTBOX.linesPerPage; - } -} +// if(UI_TEXTBOX.lineCount == 0) { +// UI_TEXTBOX.pageCount = 1; +// } else { +// int32_t div = UI_TEXTBOX.lineCount + UI_TEXTBOX.linesPerPage - 1; +// UI_TEXTBOX.pageCount = div / UI_TEXTBOX.linesPerPage; +// } +// } -void uiTextboxSetText(const char_t *text) { - assertNotNull(text, "text must not be NULL"); +// void uiTextboxSetText(const char_t *text) { +// assertNotNull(text, "text must not be NULL"); - stringCopy(UI_TEXTBOX.text, text, UI_TEXTBOX_TEXT_MAX); - UI_TEXTBOX.currentPage = 0; - UI_TEXTBOX.scroll = 0; - uiTextboxBuildLayout(); -} +// stringCopy(UI_TEXTBOX.text, text, UI_TEXTBOX_TEXT_MAX); +// UI_TEXTBOX.currentPage = 0; +// UI_TEXTBOX.scroll = 0; +// uiTextboxBuildLayout(); +// } -errorret_t uiTextboxUpdate(void) { - #ifdef DUSK_TIME_DYNAMIC - if(TIME.dynamicUpdate) errorOk(); - #endif +// errorret_t uiTextboxUpdate(void) { +// #ifdef DUSK_TIME_DYNAMIC +// if(TIME.dynamicUpdate) errorOk(); +// #endif - bool_t wasComplete = uiTextboxPageIsComplete(); +// bool_t wasComplete = uiTextboxPageIsComplete(); - if(!wasComplete) { - UI_TEXTBOX.scroll += UI_TEXTBOX_SCROLL_CHARS_PER_TICK; - } +// if(!wasComplete) { +// UI_TEXTBOX.scroll += UI_TEXTBOX_SCROLL_CHARS_PER_TICK; +// } - if(inputPressed(UI_TEXTBOX.advanceAction)) { - if(!uiTextboxPageIsComplete()) { - UI_TEXTBOX.scroll = uiTextboxGetPageCharCount(); - } else if(uiTextboxHasNextPage()) { - uiTextboxNextPage(); - } - } +// if(inputPressed(UI_TEXTBOX.advanceAction)) { +// if(!uiTextboxPageIsComplete()) { +// UI_TEXTBOX.scroll = uiTextboxGetPageCharCount(); +// } else if(uiTextboxHasNextPage()) { +// uiTextboxNextPage(); +// } +// } - if(!wasComplete && uiTextboxPageIsComplete()) { - eventInvoke(&UI_TEXTBOX.onPageComplete, &UI_TEXTBOX); - if(!uiTextboxHasNextPage()) { - eventInvoke(&UI_TEXTBOX.onLastPage, &UI_TEXTBOX); - } - } +// if(!wasComplete && uiTextboxPageIsComplete()) { +// eventInvoke(&UI_TEXTBOX.onPageComplete, &UI_TEXTBOX); +// if(!uiTextboxHasNextPage()) { +// eventInvoke(&UI_TEXTBOX.onLastPage, &UI_TEXTBOX); +// } +// } - errorOk(); -} +// errorOk(); +// } -int32_t uiTextboxGetPageCharCount(void) { - int32_t first = UI_TEXTBOX.currentPage * UI_TEXTBOX.linesPerPage; - int32_t last = first + UI_TEXTBOX.linesPerPage; - if(last > UI_TEXTBOX.lineCount) last = UI_TEXTBOX.lineCount; - int32_t total = 0; - for(int32_t i = first; i < last; i++) { - total += UI_TEXTBOX.lines[i].count; - } - return total; -} +// int32_t uiTextboxGetPageCharCount(void) { +// int32_t first = UI_TEXTBOX.currentPage * UI_TEXTBOX.linesPerPage; +// int32_t last = first + UI_TEXTBOX.linesPerPage; +// if(last > UI_TEXTBOX.lineCount) last = UI_TEXTBOX.lineCount; +// int32_t total = 0; +// for(int32_t i = first; i < last; i++) { +// total += UI_TEXTBOX.lines[i].count; +// } +// return total; +// } -bool_t uiTextboxPageIsComplete(void) { - return UI_TEXTBOX.scroll >= uiTextboxGetPageCharCount(); -} +// bool_t uiTextboxPageIsComplete(void) { +// return UI_TEXTBOX.scroll >= uiTextboxGetPageCharCount(); +// } -bool_t uiTextboxHasNextPage(void) { - return UI_TEXTBOX.currentPage + 1 < UI_TEXTBOX.pageCount; -} +// bool_t uiTextboxHasNextPage(void) { +// return UI_TEXTBOX.currentPage + 1 < UI_TEXTBOX.pageCount; +// } -void uiTextboxNextPage(void) { - if(!uiTextboxHasNextPage()) return; - UI_TEXTBOX.currentPage++; - UI_TEXTBOX.scroll = 0; -} +// void uiTextboxNextPage(void) { +// if(!uiTextboxHasNextPage()) return; +// UI_TEXTBOX.currentPage++; +// UI_TEXTBOX.scroll = 0; +// } -errorret_t uiTextboxDraw(void) { - if(UI_TEXTBOX.lineCount == 0 || UI_TEXTBOX.text[0] == '\0') errorOk(); +// errorret_t uiTextboxDraw(void) { +// if(UI_TEXTBOX.lineCount == 0 || UI_TEXTBOX.text[0] == '\0') errorOk(); - errorChain(uiFrameDraw( - &UI_TEXTBOX.frame, - UI_TEXTBOX.x, UI_TEXTBOX.y, - UI_TEXTBOX.width, UI_TEXTBOX.height - )); - errorChain(spriteBatchFlush()); +// errorChain(uiFrameDraw( +// &UI_TEXTBOX.frame, +// UI_TEXTBOX.x, UI_TEXTBOX.y, +// UI_TEXTBOX.width, UI_TEXTBOX.height +// )); +// errorChain(spriteBatchFlush()); - shadermaterial_t textMaterial = { - .unlit = { - .color = UI_TEXTBOX.textColor, - .texture = UI_TEXTBOX.font->texture - } - }; +// shadermaterial_t textMaterial = { +// .unlit = { +// .color = UI_TEXTBOX.textColor, +// .texture = UI_TEXTBOX.font->texture +// } +// }; - float_t frameTileW = (float_t)UI_TEXTBOX.frame.tileset.tileWidth; - float_t frameTileH = (float_t)UI_TEXTBOX.frame.tileset.tileHeight; - float_t fontW = (float_t)UI_TEXTBOX.font->tileset->tileWidth; - float_t fontH = (float_t)UI_TEXTBOX.font->tileset->tileHeight; - float_t contentX = UI_TEXTBOX.x + frameTileW; - float_t contentY = UI_TEXTBOX.y + frameTileH; +// float_t frameTileW = (float_t)UI_TEXTBOX.frame.tileset.tileWidth; +// float_t frameTileH = (float_t)UI_TEXTBOX.frame.tileset.tileHeight; +// float_t fontW = (float_t)UI_TEXTBOX.font->tileset->tileWidth; +// float_t fontH = (float_t)UI_TEXTBOX.font->tileset->tileHeight; +// float_t contentX = UI_TEXTBOX.x + frameTileW; +// float_t contentY = UI_TEXTBOX.y + frameTileH; - int32_t pageFirst = UI_TEXTBOX.currentPage * UI_TEXTBOX.linesPerPage; - int32_t pageLast = pageFirst + UI_TEXTBOX.linesPerPage; - if(pageLast > UI_TEXTBOX.lineCount) pageLast = UI_TEXTBOX.lineCount; +// int32_t pageFirst = UI_TEXTBOX.currentPage * UI_TEXTBOX.linesPerPage; +// int32_t pageLast = pageFirst + UI_TEXTBOX.linesPerPage; +// if(pageLast > UI_TEXTBOX.lineCount) pageLast = UI_TEXTBOX.lineCount; - int32_t charsLeft = UI_TEXTBOX.scroll; +// int32_t charsLeft = UI_TEXTBOX.scroll; - for(int32_t li = pageFirst; li < pageLast && charsLeft > 0; li++) { - uitextboxline_t *line = &UI_TEXTBOX.lines[li]; - int32_t visible = line->count < charsLeft ? line->count : charsLeft; - float_t lineY = contentY + - (float_t)(li - pageFirst) * (fontH + UI_TEXTBOX_LINE_SPACING); +// for(int32_t li = pageFirst; li < pageLast && charsLeft > 0; li++) { +// uitextboxline_t *line = &UI_TEXTBOX.lines[li]; +// int32_t visible = line->count < charsLeft ? line->count : charsLeft; +// float_t lineY = contentY + +// (float_t)(li - pageFirst) * (fontH + UI_TEXTBOX_LINE_SPACING); - for(int32_t ci = 0; ci < visible; ci++) { - char_t c = UI_TEXTBOX.text[line->start + ci]; - if(c == ' ') continue; - spritebatchsprite_t sprite = textGetSprite( - (vec2){ contentX + (float_t)ci * fontW, lineY }, - c, - UI_TEXTBOX.font - ); - errorChain(spriteBatchBuffer(&sprite, 1, &SHADER_UNLIT, textMaterial)); - } +// for(int32_t ci = 0; ci < visible; ci++) { +// char_t c = UI_TEXTBOX.text[line->start + ci]; +// if(c == ' ') continue; +// spritebatchsprite_t sprite = textGetSprite( +// (vec2){ contentX + (float_t)ci * fontW, lineY }, +// c, +// UI_TEXTBOX.font +// ); +// errorChain(spriteBatchBuffer(&sprite, 1, &SHADER_UNLIT, textMaterial)); +// } - charsLeft -= visible; - } +// charsLeft -= visible; +// } - errorChain(spriteBatchFlush()); - errorOk(); -} +// errorChain(spriteBatchFlush()); +// errorOk(); +// } -errorret_t uiTextboxDispose(void) { - uiFrameDispose(&UI_TEXTBOX.frame); - UI_TEXTBOX.font = NULL; - errorOk(); -} +// errorret_t uiTextboxDispose(void) { +// uiFrameDispose(&UI_TEXTBOX.frame); +// UI_TEXTBOX.font = NULL; +// errorOk(); +// } diff --git a/src/dusk/ui/uitextbox.h b/src/dusk/ui/uitextbox.h index 60e45a90..29606c20 100644 --- a/src/dusk/ui/uitextbox.h +++ b/src/dusk/ui/uitextbox.h @@ -1,121 +1,121 @@ -/** - * Copyright (c) 2026 Dominic Masters - * - * This software is released under the MIT License. - * https://opensource.org/licenses/MIT - */ +// /** +// * Copyright (c) 2026 Dominic Masters +// * +// * This software is released under the MIT License. +// * https://opensource.org/licenses/MIT +// */ -#pragma once -#include "uiframe.h" -#include "display/text/text.h" -#include "input/inputaction.h" -#include "event/event.h" +// #pragma once +// #include "ui/frame/uiframe.h" +// #include "display/text/text.h" +// #include "input/inputaction.h" +// #include "event/event.h" -#define UI_TEXTBOX_TEXT_MAX 1024 -#define UI_TEXTBOX_LINES_MAX 64 -#define UI_TEXTBOX_SCROLL_CHARS_PER_TICK 1 -#define UI_TEXTBOX_LINES_PER_PAGE_MAX 3 -#define UI_TEXTBOX_LINE_SPACING 0.0f +// #define UI_TEXTBOX_TEXT_MAX 1024 +// #define UI_TEXTBOX_LINES_MAX 64 +// #define UI_TEXTBOX_SCROLL_CHARS_PER_TICK 1 +// #define UI_TEXTBOX_LINES_PER_PAGE_MAX 3 +// #define UI_TEXTBOX_LINE_SPACING 0.0f -typedef struct { - int32_t start; - int32_t count; -} uitextboxline_t; +// typedef struct { +// int32_t start; +// int32_t count; +// } uitextboxline_t; -typedef struct { - uiframe_t frame; +// typedef struct { +// uiframe_t frame; - font_t *font; - color_t textColor; +// font_t *font; +// color_t textColor; - float_t x, y, width, height; +// float_t x, y, width, height; - char_t text[UI_TEXTBOX_TEXT_MAX]; +// char_t text[UI_TEXTBOX_TEXT_MAX]; - uitextboxline_t lines[UI_TEXTBOX_LINES_MAX]; - int32_t lineCount; - int32_t charsPerLine; - int32_t linesPerPage; - int32_t pageCount; +// uitextboxline_t lines[UI_TEXTBOX_LINES_MAX]; +// int32_t lineCount; +// int32_t charsPerLine; +// int32_t linesPerPage; +// int32_t pageCount; - int32_t currentPage; - int32_t scroll; - inputaction_t advanceAction; +// int32_t currentPage; +// int32_t scroll; +// inputaction_t advanceAction; - eventcallback_t onPageCompleteCallbacks[4]; - void *onPageCompleteUsers[4]; - event_t onPageComplete; - eventcallback_t onLastPageCallbacks[4]; - void *onLastPageUsers[4]; - event_t onLastPage; -} uitextbox_t; +// eventcallback_t onPageCompleteCallbacks[4]; +// void *onPageCompleteUsers[4]; +// event_t onPageComplete; +// eventcallback_t onLastPageCallbacks[4]; +// void *onLastPageUsers[4]; +// event_t onLastPage; +// } uitextbox_t; -extern uitextbox_t UI_TEXTBOX; +// extern uitextbox_t UI_TEXTBOX; -/** - * Initializes UI_TEXTBOX. Sets position and size from current screen - * dimensions and wires up the default font and frame textures. - * Call after displayInit(). - * - * @return Any error that occurs. - */ -errorret_t uiTextboxInit(void); +// /** +// * Initializes UI_TEXTBOX. Sets position and size from current screen +// * dimensions and wires up the default font and frame textures. +// * Call after displayInit(). +// * +// * @return Any error that occurs. +// */ +// errorret_t uiTextboxInit(void); -/** - * Rebuilds the word-wrap and page layout from the current text and dimensions. - * Called automatically by uiTextboxSetText. - */ -void uiTextboxBuildLayout(void); +// /** +// * Rebuilds the word-wrap and page layout from the current text and dimensions. +// * Called automatically by uiTextboxSetText. +// */ +// void uiTextboxBuildLayout(void); -/** - * Copies text into UI_TEXTBOX and rebuilds the word-wrap / page layout. - * Resets currentPage and scroll to 0. - * - * @param text Null-terminated source string. - */ -void uiTextboxSetText(const char_t *text); +// /** +// * Copies text into UI_TEXTBOX and rebuilds the word-wrap / page layout. +// * Resets currentPage and scroll to 0. +// * +// * @param text Null-terminated source string. +// */ +// void uiTextboxSetText(const char_t *text); -/** - * Advances the typewriter scroll by UI_TEXTBOX_SCROLL_CHARS_PER_TICK. - * Skipped on dynamic ticks when DUSK_TIME_DYNAMIC is defined. - * - * @return Any error that occurs. - */ -errorret_t uiTextboxUpdate(void); +// /** +// * Advances the typewriter scroll by UI_TEXTBOX_SCROLL_CHARS_PER_TICK. +// * Skipped on dynamic ticks when DUSK_TIME_DYNAMIC is defined. +// * +// * @return Any error that occurs. +// */ +// errorret_t uiTextboxUpdate(void); -/** - * Draws the frame and the currently visible text, including a final flush. - * - * @return Any error that occurs. - */ -errorret_t uiTextboxDraw(void); +// /** +// * Draws the frame and the currently visible text, including a final flush. +// * +// * @return Any error that occurs. +// */ +// errorret_t uiTextboxDraw(void); -/** - * Returns the total char count for the current page. - * - * @return Total chars on current page. - */ -int32_t uiTextboxGetPageCharCount(void); +// /** +// * Returns the total char count for the current page. +// * +// * @return Total chars on current page. +// */ +// int32_t uiTextboxGetPageCharCount(void); -/** - * Returns true when scroll has fully revealed the current page. - */ -bool_t uiTextboxPageIsComplete(void); +// /** +// * Returns true when scroll has fully revealed the current page. +// */ +// bool_t uiTextboxPageIsComplete(void); -/** - * Returns true when there is at least one more page after the current one. - */ -bool_t uiTextboxHasNextPage(void); +// /** +// * Returns true when there is at least one more page after the current one. +// */ +// bool_t uiTextboxHasNextPage(void); -/** - * Advances to the next page and resets scroll to 0. - * Has no effect if already on the last page. - */ -void uiTextboxNextPage(void); +// /** +// * Advances to the next page and resets scroll to 0. +// * Has no effect if already on the last page. +// */ +// void uiTextboxNextPage(void); -/** - * Disposes of UI_TEXTBOX, nulling out texture pointers. - * - * @return Any error that occurs. - */ -errorret_t uiTextboxDispose(void); +// /** +// * Disposes of UI_TEXTBOX, nulling out texture pointers. +// * +// * @return Any error that occurs. +// */ +// errorret_t uiTextboxDispose(void); -- 2.52.0 From 335d79f2c4ec6b8ff59f7a48a8cebd72306da420 Mon Sep 17 00:00:00 2001 From: Dominic Masters Date: Thu, 25 Jun 2026 18:01:11 -0500 Subject: [PATCH 05/16] NUked archive --- archive/asset/args.py | 12 - archive/asset/bundle/__main__.py | 66 --- archive/asset/cache.py | 12 - archive/asset/path.py | 10 - archive/asset/process/asset.py | 33 -- archive/asset/process/image.py | 134 ------ archive/asset/process/language.py | 228 ---------- archive/asset/process/map.py | 154 ------- archive/asset/process/palette.py | 96 ----- archive/asset/process/script.py | 43 -- archive/asset/process/tileset.py | 178 -------- archive/assetpalette.c | 47 --- archive/assetpalette.h | 30 -- archive/camera/CMakeLists.txt | 10 - archive/camera/camera.c | 107 ----- archive/camera/camera.h | 97 ----- archive/dusk/chunk.py | 158 ------- archive/dusk/defs.py | 49 --- archive/dusk/entity.py | 90 ---- archive/dusk/event.py | 18 - archive/dusk/map.py | 259 ------------ archive/dusk/region.py | 141 ------- archive/dusk/tile.py | 206 --------- archive/editor/__main__.py | 57 --- archive/editor/cutscene/cutsceneitemeditor.py | 58 --- archive/editor/cutscene/cutscenemenubar.py | 54 --- archive/editor/cutscene/cutscenetext.py | 21 - archive/editor/cutscene/cutscenewait.py | 20 - archive/editor/cutscenetool.py | 101 ----- archive/editor/langtool.py | 202 --------- archive/editor/map/camera.py | 55 --- archive/editor/map/chunkpanel.py | 80 ---- archive/editor/map/entitypanel.py | 154 ------- archive/editor/map/glwidget.py | 41 -- archive/editor/map/grid.py | 46 --- archive/editor/map/mapinfopanel.py | 83 ---- archive/editor/map/mapleftpanel.py | 51 --- archive/editor/map/menubar.py | 46 --- archive/editor/map/regionpanel.py | 15 - archive/editor/map/selectbox.py | 44 -- archive/editor/map/statusbar.py | 18 - archive/editor/map/vertexbuffer.py | 80 ---- archive/editor/maptool.py | 143 ------- archive/loader/CMakeLists.txt | 13 - archive/loader/assetlanguage.c | 45 -- archive/loader/assetlanguage.h | 61 --- archive/loader/assetscript.c | 58 --- archive/loader/assetscript.h | 57 --- archive/loader/assettexture.c | 78 ---- archive/loader/assettexture.h | 39 -- archive/loader/assettileset.c | 70 ---- archive/loader/assettileset.h | 32 -- archive/map/CMakeLists.txt | 6 - archive/map/testmap/CMakeLists.txt | 6 - archive/map/testmap/chunks/-1_-1_0.json | 1 - archive/map/testmap/chunks/-1_-1_1.json | 1 - archive/map/testmap/chunks/-1_0_0.json | 1 - archive/map/testmap/chunks/-2_1_0.json | 1 - archive/map/testmap/chunks/0_-1_0.json | 1 - archive/map/testmap/chunks/0_-1_1.json | 1 - archive/map/testmap/chunks/0_0_0.json | 1 - archive/map/testmap/chunks/1_0_0.json | 1 - archive/map/testmap/chunks/2_0_0.json | 1 - archive/map/testmap/chunks/3_0_0.json | 1 - archive/map/testmap/chunks/4_0_0.json | 1 - archive/map/testmap/chunks/5_0_0.json | 1 - archive/map/testmap/testmap.json | 3 - archive/modulemap.h | 49 --- archive/platform/CMakeLists.txt | 12 - archive/platform/dolphin.c | 92 ----- archive/platform/dolphin.h | 27 -- archive/platform/psp.c | 12 - archive/platform/psp.h | 13 - archive/platform/sdl2.c | 32 -- archive/platform/sdl2.h | 35 -- archive/rpg/CMakeLists.txt | 17 - archive/rpg/cutscene/CMakeLists.txt | 14 - archive/rpg/cutscene/cutscene.h | 14 - archive/rpg/cutscene/cutscenemode.c | 19 - archive/rpg/cutscene/cutscenemode.h | 26 -- archive/rpg/cutscene/cutscenesystem.c | 56 --- archive/rpg/cutscene/cutscenesystem.h | 50 --- archive/rpg/cutscene/item/CMakeLists.txt | 10 - archive/rpg/cutscene/item/cutscenecallback.h | 11 - archive/rpg/cutscene/item/cutsceneitem.c | 54 --- archive/rpg/cutscene/item/cutsceneitem.h | 53 --- archive/rpg/cutscene/item/cutscenetext.h | 14 - archive/rpg/cutscene/item/cutscenewait.h | 12 - archive/rpg/cutscene/scene/testcutscene.h | 30 -- archive/rpg/entity/CMakeLists.txt | 14 - archive/rpg/entity/entity.c | 200 --------- archive/rpg/entity/entity.h | 77 ---- archive/rpg/entity/entityanim.c | 9 - archive/rpg/entity/entityanim.h | 18 - archive/rpg/entity/entitydir.c | 51 --- archive/rpg/entity/entitydir.h | 50 --- archive/rpg/entity/entitytype.h | 55 --- archive/rpg/entity/npc.c | 31 -- archive/rpg/entity/npc.h | 37 -- archive/rpg/entity/player.c | 54 --- archive/rpg/entity/player.h | 43 -- archive/rpg/overworld/CMakeLists.txt | 13 - archive/rpg/overworld/chunk.c | 20 - archive/rpg/overworld/chunk.h | 38 -- archive/rpg/overworld/map.c | 269 ------------ archive/rpg/overworld/map.h | 106 ----- archive/rpg/overworld/tile.c | 35 -- archive/rpg/overworld/tile.h | 27 -- archive/rpg/overworld/worldpos.c | 97 ----- archive/rpg/overworld/worldpos.h | 75 ---- archive/rpg/rpg.c | 64 --- archive/rpg/rpg.h | 32 -- archive/rpg/rpgcamera.c | 52 --- archive/rpg/rpgcamera.h | 39 -- archive/rpg/rpgtextbox.c | 39 -- archive/rpg/rpgtextbox.h | 52 --- archive/scene/CMakeLists.txt | 13 - archive/scene/scene.h | 24 -- archive/scene/scene/CMakeLists.txt | 13 - archive/scene/scene/scenemap.c | 212 ---------- archive/scene/scene/scenemap.h | 32 -- archive/scene/scene/scenetest.c | 26 -- archive/scene/scene/scenetest.h | 27 -- archive/scene/scenedata.h | 12 - archive/scene/scenemanager.c | 119 ------ archive/scene/scenemanager.h | 63 --- archive/scriptfuncentity.h | 134 ------ archive/ui/CMakeLists.txt | 17 - archive/ui/element/CMakeLists.txt | 9 - archive/ui/element/uielementtype.h | 16 - archive/ui/ui.c | 67 --- archive/ui/ui.h | 51 --- archive/ui/uidebug.c | 98 ----- archive/ui/uidebug.h | 18 - archive/ui/uiframe.c | 390 ------------------ archive/ui/uiframe.h | 61 --- archive/ui/uitext.c | 111 ----- archive/ui/uitext.h | 66 --- archive/ui/uitextbox.c | 45 -- archive/ui/uitextbox.h | 19 - archive/ui2/CMakeLists.txt | 9 - archive/ui2/ui2.c | 8 - archive/ui2/ui2.h | 10 - src/dusk/display/spritebatch/CMakeLists.txt | 1 + src/dusk/display/spritebatch/spritebatch.h | 8 +- .../display/spritebatch/spritebatchsprite.c | 41 ++ .../display/spritebatch/spritebatchsprite.h | 40 ++ src/dusk/display/text/text.c | 2 + src/dusk/input/input.h | 12 +- src/dusk/rpg/entity/player.c | 6 + src/dusk/ui/focus/uifocus.c | 140 +++---- src/dusk/ui/focus/uifocus.h | 22 +- src/dusk/ui/frame/uiframe.c | 163 +++----- src/dusk/ui/frame/uiframe.h | 6 +- src/dusk/ui/frame/uisettings.c | 51 ++- src/dusk/ui/frame/uisettings.h | 13 +- 156 files changed, 293 insertions(+), 8158 deletions(-) delete mode 100644 archive/asset/args.py delete mode 100644 archive/asset/bundle/__main__.py delete mode 100644 archive/asset/cache.py delete mode 100644 archive/asset/path.py delete mode 100644 archive/asset/process/asset.py delete mode 100644 archive/asset/process/image.py delete mode 100644 archive/asset/process/language.py delete mode 100644 archive/asset/process/map.py delete mode 100644 archive/asset/process/palette.py delete mode 100644 archive/asset/process/script.py delete mode 100644 archive/asset/process/tileset.py delete mode 100644 archive/assetpalette.c delete mode 100644 archive/assetpalette.h delete mode 100644 archive/camera/CMakeLists.txt delete mode 100644 archive/camera/camera.c delete mode 100644 archive/camera/camera.h delete mode 100644 archive/dusk/chunk.py delete mode 100644 archive/dusk/defs.py delete mode 100644 archive/dusk/entity.py delete mode 100644 archive/dusk/event.py delete mode 100644 archive/dusk/map.py delete mode 100644 archive/dusk/region.py delete mode 100644 archive/dusk/tile.py delete mode 100755 archive/editor/__main__.py delete mode 100644 archive/editor/cutscene/cutsceneitemeditor.py delete mode 100644 archive/editor/cutscene/cutscenemenubar.py delete mode 100644 archive/editor/cutscene/cutscenetext.py delete mode 100644 archive/editor/cutscene/cutscenewait.py delete mode 100644 archive/editor/cutscenetool.py delete mode 100644 archive/editor/langtool.py delete mode 100644 archive/editor/map/camera.py delete mode 100644 archive/editor/map/chunkpanel.py delete mode 100644 archive/editor/map/entitypanel.py delete mode 100644 archive/editor/map/glwidget.py delete mode 100644 archive/editor/map/grid.py delete mode 100644 archive/editor/map/mapinfopanel.py delete mode 100644 archive/editor/map/mapleftpanel.py delete mode 100644 archive/editor/map/menubar.py delete mode 100644 archive/editor/map/regionpanel.py delete mode 100644 archive/editor/map/selectbox.py delete mode 100644 archive/editor/map/statusbar.py delete mode 100644 archive/editor/map/vertexbuffer.py delete mode 100644 archive/editor/maptool.py delete mode 100644 archive/loader/CMakeLists.txt delete mode 100644 archive/loader/assetlanguage.c delete mode 100644 archive/loader/assetlanguage.h delete mode 100644 archive/loader/assetscript.c delete mode 100644 archive/loader/assetscript.h delete mode 100644 archive/loader/assettexture.c delete mode 100644 archive/loader/assettexture.h delete mode 100644 archive/loader/assettileset.c delete mode 100644 archive/loader/assettileset.h delete mode 100644 archive/map/CMakeLists.txt delete mode 100644 archive/map/testmap/CMakeLists.txt delete mode 100644 archive/map/testmap/chunks/-1_-1_0.json delete mode 100644 archive/map/testmap/chunks/-1_-1_1.json delete mode 100644 archive/map/testmap/chunks/-1_0_0.json delete mode 100644 archive/map/testmap/chunks/-2_1_0.json delete mode 100644 archive/map/testmap/chunks/0_-1_0.json delete mode 100644 archive/map/testmap/chunks/0_-1_1.json delete mode 100644 archive/map/testmap/chunks/0_0_0.json delete mode 100644 archive/map/testmap/chunks/1_0_0.json delete mode 100644 archive/map/testmap/chunks/2_0_0.json delete mode 100644 archive/map/testmap/chunks/3_0_0.json delete mode 100644 archive/map/testmap/chunks/4_0_0.json delete mode 100644 archive/map/testmap/chunks/5_0_0.json delete mode 100644 archive/map/testmap/testmap.json delete mode 100644 archive/modulemap.h delete mode 100644 archive/platform/CMakeLists.txt delete mode 100644 archive/platform/dolphin.c delete mode 100644 archive/platform/dolphin.h delete mode 100644 archive/platform/psp.c delete mode 100644 archive/platform/psp.h delete mode 100644 archive/platform/sdl2.c delete mode 100644 archive/platform/sdl2.h delete mode 100644 archive/rpg/CMakeLists.txt delete mode 100644 archive/rpg/cutscene/CMakeLists.txt delete mode 100644 archive/rpg/cutscene/cutscene.h delete mode 100644 archive/rpg/cutscene/cutscenemode.c delete mode 100644 archive/rpg/cutscene/cutscenemode.h delete mode 100644 archive/rpg/cutscene/cutscenesystem.c delete mode 100644 archive/rpg/cutscene/cutscenesystem.h delete mode 100755 archive/rpg/cutscene/item/CMakeLists.txt delete mode 100644 archive/rpg/cutscene/item/cutscenecallback.h delete mode 100644 archive/rpg/cutscene/item/cutsceneitem.c delete mode 100644 archive/rpg/cutscene/item/cutsceneitem.h delete mode 100644 archive/rpg/cutscene/item/cutscenetext.h delete mode 100644 archive/rpg/cutscene/item/cutscenewait.h delete mode 100755 archive/rpg/cutscene/scene/testcutscene.h delete mode 100644 archive/rpg/entity/CMakeLists.txt delete mode 100644 archive/rpg/entity/entity.c delete mode 100644 archive/rpg/entity/entity.h delete mode 100644 archive/rpg/entity/entityanim.c delete mode 100644 archive/rpg/entity/entityanim.h delete mode 100644 archive/rpg/entity/entitydir.c delete mode 100644 archive/rpg/entity/entitydir.h delete mode 100644 archive/rpg/entity/entitytype.h delete mode 100644 archive/rpg/entity/npc.c delete mode 100644 archive/rpg/entity/npc.h delete mode 100644 archive/rpg/entity/player.c delete mode 100644 archive/rpg/entity/player.h delete mode 100644 archive/rpg/overworld/CMakeLists.txt delete mode 100644 archive/rpg/overworld/chunk.c delete mode 100644 archive/rpg/overworld/chunk.h delete mode 100644 archive/rpg/overworld/map.c delete mode 100644 archive/rpg/overworld/map.h delete mode 100644 archive/rpg/overworld/tile.c delete mode 100644 archive/rpg/overworld/tile.h delete mode 100644 archive/rpg/overworld/worldpos.c delete mode 100644 archive/rpg/overworld/worldpos.h delete mode 100644 archive/rpg/rpg.c delete mode 100644 archive/rpg/rpg.h delete mode 100644 archive/rpg/rpgcamera.c delete mode 100644 archive/rpg/rpgcamera.h delete mode 100644 archive/rpg/rpgtextbox.c delete mode 100644 archive/rpg/rpgtextbox.h delete mode 100644 archive/scene/CMakeLists.txt delete mode 100644 archive/scene/scene.h delete mode 100644 archive/scene/scene/CMakeLists.txt delete mode 100644 archive/scene/scene/scenemap.c delete mode 100644 archive/scene/scene/scenemap.h delete mode 100644 archive/scene/scene/scenetest.c delete mode 100644 archive/scene/scene/scenetest.h delete mode 100644 archive/scene/scenedata.h delete mode 100644 archive/scene/scenemanager.c delete mode 100644 archive/scene/scenemanager.h delete mode 100644 archive/scriptfuncentity.h delete mode 100644 archive/ui/CMakeLists.txt delete mode 100644 archive/ui/element/CMakeLists.txt delete mode 100644 archive/ui/element/uielementtype.h delete mode 100644 archive/ui/ui.c delete mode 100644 archive/ui/ui.h delete mode 100644 archive/ui/uidebug.c delete mode 100644 archive/ui/uidebug.h delete mode 100644 archive/ui/uiframe.c delete mode 100644 archive/ui/uiframe.h delete mode 100644 archive/ui/uitext.c delete mode 100644 archive/ui/uitext.h delete mode 100644 archive/ui/uitextbox.c delete mode 100644 archive/ui/uitextbox.h delete mode 100644 archive/ui2/CMakeLists.txt delete mode 100644 archive/ui2/ui2.c delete mode 100644 archive/ui2/ui2.h create mode 100644 src/dusk/display/spritebatch/spritebatchsprite.c create mode 100644 src/dusk/display/spritebatch/spritebatchsprite.h diff --git a/archive/asset/args.py b/archive/asset/args.py deleted file mode 100644 index d086b957..00000000 --- a/archive/asset/args.py +++ /dev/null @@ -1,12 +0,0 @@ -import sys, os -import argparse - -# Check if the script is run with the correct arguments -parser = argparse.ArgumentParser(description="Generate chunk header files") -parser.add_argument('--assets', required=True, help='Dir to output built assets') -parser.add_argument('--headers-dir', required=True, help='Directory to output individual asset headers (required for header build)') -parser.add_argument('--output-headers', help='Output header file for built assets (required for header build)') -parser.add_argument('--output-assets', required=True, help='Output directory for built assets') -parser.add_argument('--output-file', required=True, help='Output file for built assets (required for wad build)') -parser.add_argument('--input', required=True, help='Input assets to process', nargs='+') -args = parser.parse_args() \ No newline at end of file diff --git a/archive/asset/bundle/__main__.py b/archive/asset/bundle/__main__.py deleted file mode 100644 index ebfdc722..00000000 --- a/archive/asset/bundle/__main__.py +++ /dev/null @@ -1,66 +0,0 @@ -import sys, os -from tools.asset.args import args -from tools.asset.process.asset import processAsset -from tools.asset.process.palette import processPaletteList -from tools.asset.process.tileset import processTilesetList -from tools.asset.process.language import processLanguageList -from tools.asset.path import getBuiltAssetsRelativePath -import zipfile - -# Parse input file args. -inputAssets = [] -for inputArg in args.input: - files = inputArg.split('$') - for file in files: - if str(file).strip() == '': - continue - - pieces = file.split('#') - - if len(pieces) < 2: - print(f"Error: Invalid input asset format '{file}'. Expected format: type#path[#option1%option2...]") - sys.exit(1) - - options = {} - if len(pieces) > 2: - optionParts = pieces[2].split('%') - for part in optionParts: - partSplit = part.split('=') - - if len(partSplit) < 1: - continue - if len(partSplit) == 2: - options[partSplit[0]] = partSplit[1] - else: - options[partSplit[0]] = True - - inputAssets.append({ - 'type': pieces[0], - 'path': pieces[1], - 'options': options - }) - -if not inputAssets: - print("Error: No input assets provided.") - sys.exit(1) - -# Process each asset. -files = [] -for asset in inputAssets: - asset = processAsset(asset) - files.extend(asset['files']) - -# Generate additional files -files.extend(processLanguageList()['files']) - -# Take assets and add to a zip archive. -outputFileName = args.output_file -print(f"Creating output file: {outputFileName}") -with zipfile.ZipFile(outputFileName, 'w') as zipf: - for file in files: - relativeOutputPath = getBuiltAssetsRelativePath(file) - zipf.write(file, arcname=relativeOutputPath) - -# Generate additional headers. -processPaletteList() -processTilesetList() \ No newline at end of file diff --git a/archive/asset/cache.py b/archive/asset/cache.py deleted file mode 100644 index 9c1d028e..00000000 --- a/archive/asset/cache.py +++ /dev/null @@ -1,12 +0,0 @@ -processedAssets = {} - -def assetGetCache(assetPath): - if assetPath in processedAssets: - return processedAssets[assetPath] - return None - -def assetCache(assetPath, processedData): - if assetPath in processedAssets: - return processedAssets[assetPath] - processedAssets[assetPath] = processedData - return processedData \ No newline at end of file diff --git a/archive/asset/path.py b/archive/asset/path.py deleted file mode 100644 index d15b8bfe..00000000 --- a/archive/asset/path.py +++ /dev/null @@ -1,10 +0,0 @@ -import os -from tools.asset.args import args - -def getAssetRelativePath(fullPath): - # Get the relative path to the asset - return os.path.relpath(fullPath, start=args.assets).replace('\\', '/') - -def getBuiltAssetsRelativePath(fullPath): - # Get the relative path to the built asset - return os.path.relpath(fullPath, start=args.output_assets).replace('\\', '/') \ No newline at end of file diff --git a/archive/asset/process/asset.py b/archive/asset/process/asset.py deleted file mode 100644 index fcce3023..00000000 --- a/archive/asset/process/asset.py +++ /dev/null @@ -1,33 +0,0 @@ -import sys -# from processtileset import processTileset -from tools.asset.process.image import processImage -from tools.asset.process.palette import processPalette -from tools.asset.process.tileset import processTileset -from tools.asset.process.map import processMap -from tools.asset.process.language import processLanguage -from tools.asset.process.script import processScript - -processedAssets = [] - -def processAsset(asset): - if asset['path'] in processedAssets: - return - processedAssets.append(asset['path']) - - # Handle tiled tilesets - t = asset['type'].lower() - if t == 'palette': - return processPalette(asset) - elif t == 'image': - return processImage(asset) - elif t == 'tileset': - return processTileset(asset) - elif t == 'map': - return processMap(asset) - elif t == 'language': - return processLanguage(asset) - elif t == 'script': - return processScript(asset) - else: - print(f"Error: Unknown asset type '{asset['type']}' for path '{asset['path']}'") - sys.exit(1) \ No newline at end of file diff --git a/archive/asset/process/image.py b/archive/asset/process/image.py deleted file mode 100644 index 0daa9177..00000000 --- a/archive/asset/process/image.py +++ /dev/null @@ -1,134 +0,0 @@ -import os -import sys -from PIL import Image -from tools.asset.process.palette import extractPaletteFromImage, palettes -from tools.asset.args import args -from tools.asset.path import getAssetRelativePath -from tools.asset.cache import assetGetCache, assetCache - -images = [] - -def processImage(asset): - cache = assetGetCache(asset['path']) - if cache is not None: - return cache - - type = None - if 'type' in asset['options']: - type = asset['options'].get('type', 'PALETTIZED').upper() - - if type == 'PALETTIZED' or type is None: - return assetCache(asset['path'], processPalettizedImage(asset)) - elif type == 'ALPHA': - return assetCache(asset['path'], processAlphaImage(asset)) - else: - print(f"Error: Unknown image type {type} for asset {asset['path']}") - sys.exit(1) - -def processPalettizedImage(asset): - assetPath = asset['path'] - cache = assetGetCache(assetPath) - if cache is not None: - return cache - - image = Image.open(assetPath) - imagePalette = extractPaletteFromImage(image) - - # Find palette that contains every color - palette = None - for p in palettes: - hasAllColors = True - for color in imagePalette: - for palColor in p['pixels']: - if color[0] == palColor[0] and color[1] == palColor[1] and color[2] == palColor[2] and color[3] == palColor[3]: - break - elif color[3] == 0 and palColor[3] == 0: - break - else: - print('Pallete {} does not contain color #{}'.format(p['paletteName'], '{:02x}{:02x}{:02x}{:02x}'.format(color[0], color[1], color[2], color[3]))) - hasAllColors = False - break - if hasAllColors: - palette = p - break - - if palette is None: - palette = palettes[0] # Just to avoid reference error - print(f"No matching palette found for {assetPath}!") - # Find which pixel is missing - for color in imagePalette: - if color in palette['pixels']: - continue - # Convert to hex (with alpha) - hexColor = '#{:02x}{:02x}{:02x}{:02x}'.format(color[0], color[1], color[2], color[3]) - print(f"Missing color: {hexColor} in palette {palette['paletteName']}") - sys.exit(1) - - print(f"Converting image {assetPath} to use palette") - - paletteIndexes = [] - for pixel in list(image.getdata()): - if pixel[3] == 0: - pixel = (0, 0, 0, 0) - paletteIndex = palette['pixels'].index(pixel) - paletteIndexes.append(paletteIndex) - - data = bytearray() - data.extend(b"DPI") # Dusk Palettized Image - data.extend(image.width.to_bytes(4, 'little')) # Width - data.extend(image.height.to_bytes(4, 'little')) # Height - data.append(palette['paletteIndex']) # Palette index - for paletteIndex in paletteIndexes: - if paletteIndex > 255 or paletteIndex < 0: - print(f"Error: Palette index {paletteIndex} exceeds 255!") - sys.exit(1) - data.append(paletteIndex.to_bytes(1, 'little')[0]) # Pixel index - - relative = getAssetRelativePath(assetPath) - fileNameWithoutExt = os.path.splitext(os.path.basename(assetPath))[0] - outputFileRelative = os.path.join(os.path.dirname(relative), f"{fileNameWithoutExt}.dpi") - outputFilePath = os.path.join(args.output_assets, outputFileRelative) - os.makedirs(os.path.dirname(outputFilePath), exist_ok=True) - with open(outputFilePath, "wb") as f: - f.write(data) - - outImage = { - "imagePath": outputFileRelative, - "files": [ outputFilePath ], - 'width': image.width, - 'height': image.height, - } - return assetCache(assetPath, outImage) - -def processAlphaImage(asset): - assetPath = asset['path'] - cache = assetGetCache(assetPath) - if cache is not None: - return cache - - print(f"Processing alpha image: {assetPath}") - - data = bytearray() - data.extend(b"DAI") # Dusk Alpha Image - image = Image.open(assetPath).convert("RGBA") - data.extend(image.width.to_bytes(4, 'little')) # Width - data.extend(image.height.to_bytes(4, 'little')) # Height - for pixel in list(image.getdata()): - # Only write alpha channel - data.append(pixel[3].to_bytes(1, 'little')[0]) # Pixel alpha - - relative = getAssetRelativePath(assetPath) - fileNameWithoutExt = os.path.splitext(os.path.basename(assetPath))[0] - outputFileRelative = os.path.join(os.path.dirname(relative), f"{fileNameWithoutExt}.dai") - outputFilePath = os.path.join(args.output_assets, outputFileRelative) - os.makedirs(os.path.dirname(outputFilePath), exist_ok=True) - with open(outputFilePath, "wb") as f: - f.write(data) - - outImage = { - "imagePath": outputFileRelative, - "files": [ outputFilePath ], - 'width': image.width, - 'height': image.height, - } - return assetCache(assetPath, outImage) \ No newline at end of file diff --git a/archive/asset/process/language.py b/archive/asset/process/language.py deleted file mode 100644 index 63222fef..00000000 --- a/archive/asset/process/language.py +++ /dev/null @@ -1,228 +0,0 @@ -import sys -import os -from tools.asset.args import args -from tools.asset.cache import assetCache, assetGetCache -from tools.asset.path import getAssetRelativePath -from tools.dusk.defs import defs -import polib -import re - -LANGUAGE_CHUNK_CHAR_COUNT = int(defs.get('ASSET_LANG_CHUNK_CHAR_COUNT')) - -LANGUAGE_DATA = {} -LANGUAGE_KEYS = [] - -def processLanguageList(): - # Language keys header data - headerKeys = "// Auto-generated language keys header file.\n" - headerKeys += "#pragma once\n" - headerKeys += "#include \"dusk.h\"\n\n" - - # This is the desired chunk groups list.. if a language key STARTS with any - # of the keys in this list we would "like to" put it in that chunk group. - # If there is no match, or the list is full then we will add it to the next - # available chunk group (that isn't a 'desired' one). If the chunk becomes - # full, then we attempt to make another chunk with the same prefix so that - # a second batching can occur. - desiredChunkGroups = { - 'ui': 0 - } - - # Now, for each language key, create the header reference and index. - keyIndex = 0 - languageKeyIndexes = {} - languageKeyChunk = {} - languageKeyChunkIndexes = {} - languageKeyChunkOffsets = {} - for key in LANGUAGE_KEYS: - headerKeys += f"#define {getLanguageVariableName(key)} {keyIndex}\n" - languageKeyIndexes[key] = keyIndex - keyIndex += 1 - - # Find desired chunk group - assignedChunk = None - for desiredKey in desiredChunkGroups: - if key.lower().startswith(desiredKey): - assignedChunk = desiredChunkGroups[desiredKey] - break - # If no desired chunk group matched, assign to -1 - if assignedChunk is None: - assignedChunk = -1 - languageKeyChunk[key] = assignedChunk - - # Setup header. - for lang in LANGUAGE_DATA: - if key not in LANGUAGE_DATA[lang]: - print(f"Warning: Missing translation for key '{key}' in language '{lang}'") - sys.exit(1) - - # Seal the header. - headerKeys += f"\n#define LANG_KEY_COUNT {len(LANGUAGE_KEYS)}\n" - - # Now we can generate the language string chunks. - nextChunkIndex = max(desiredChunkGroups.values()) + 1 - files = [] - - for lang in LANGUAGE_DATA: - langData = LANGUAGE_DATA[lang] - - # Key = chunkIndex, value = chunkInfo - languageChunks = {} - for key in LANGUAGE_KEYS: - keyIndex = languageKeyIndexes[key] - chunkIndex = languageKeyChunk[key] - wasSetChunk = chunkIndex != -1 - - # This will keep looping until we find a chunk - while True: - # Determine the next chunkIndex IF chunkIndex is -1 - if chunkIndex == -1: - chunkIndex = nextChunkIndex - - # Is the chunk full? - curLen = languageChunks.get(chunkIndex, {'len': 0})['len'] - newLen = curLen + len(langData[key]) - if newLen > LANGUAGE_CHUNK_CHAR_COUNT: - # Chunk is full, need to create a new chunk. - chunkIndex = -1 - if wasSetChunk: - wasSetChunk = False - else: - nextChunkIndex += 1 - continue - - # Chunk is not full, we can use it. - if chunkIndex not in languageChunks: - languageChunks[chunkIndex] = { - 'len': 0, - 'keys': [] - } - languageChunks[chunkIndex]['len'] = newLen - languageChunks[chunkIndex]['keys'].append(key) - languageKeyChunkIndexes[key] = chunkIndex - languageKeyChunkOffsets[key] = curLen - break - - # We have now chunked all the keys for this language! - langBuffer = b"" - - # Write header info - langBuffer += b'DLF' # Dusk Language File - - for key in LANGUAGE_KEYS: - # Write the chunk that this key belongs to as uint32_t - chunkIndex = languageKeyChunkIndexes[key] - langBuffer += chunkIndex.to_bytes(4, byteorder='little') - - # Write the offset for this key as uint32_t - offset = languageKeyChunkOffsets[key] - langBuffer += offset.to_bytes(4, byteorder='little') - - # Write the length of the string as uint32_t - strData = langData[key].encode('utf-8') - langBuffer += len(strData).to_bytes(4, byteorder='little') - - # Now write out each chunk's string data, packed tight and no null term. - for chunkIndex in sorted(languageChunks.keys()): - chunkInfo = languageChunks[chunkIndex] - for key in chunkInfo['keys']: - strData = langData[key].encode('utf-8') - langBuffer += strData - - # Now pad the chunk to full size - curLen = chunkInfo['len'] - if curLen < LANGUAGE_CHUNK_CHAR_COUNT: - padSize = LANGUAGE_CHUNK_CHAR_COUNT - curLen - langBuffer += b'\0' * padSize - - # Write out the language data file - outputFile = os.path.join(args.output_assets, "language", f"{lang}.dlf") - files.append(outputFile) - os.makedirs(os.path.dirname(outputFile), exist_ok=True) - with open(outputFile, "wb") as f: - f.write(langBuffer) - - # Write out the language keys header file - outputFile = os.path.join(args.headers_dir, "locale", "language", "keys.h") - os.makedirs(os.path.dirname(outputFile), exist_ok=True) - with open(outputFile, "w") as f: - f.write(headerKeys) - - # Generate language list. - langValues = {} - headerLocale = "#pragma once\n#include \"locale/localeinfo.h\"\n\n" - headerLocale += "typedef enum {\n" - count = 0 - headerLocale += f" DUSK_LOCALE_NULL = {count},\n" - count += 1 - for lang in LANGUAGE_DATA: - langKey = lang.replace('-', '_').replace(' ', '_').upper() - langValues[lang] = count - headerLocale += f" DUSK_LOCALE_{langKey} = {count},\n" - count += 1 - headerLocale += f" DUSK_LOCALE_COUNT = {count}\n" - headerLocale += "} dusklocale_t;\n\n" - - headerLocale += f"static const localeinfo_t LOCALE_INFOS[DUSK_LOCALE_COUNT] = {{\n" - for lang in LANGUAGE_DATA: - langKey = lang.replace('-', '_').replace(' ', '_').upper() - headerLocale += f" [DUSK_LOCALE_{langKey}] = {{\n" - headerLocale += f" .file = \"{lang}\"\n" - headerLocale += f" }},\n" - headerLocale += "};\n" - - headerLocale += f"static const char_t *LOCALE_SCRIPT = \n" - for lang in LANGUAGE_DATA: - langKey = lang.replace('-', '_').replace(' ', '_').upper() - langValue = langValues[lang] - headerLocale += f" \"DUSK_LOCALE_{langKey} = {langValue}\\n\"\n" - headerLocale += ";\n" - - # Write out the locale enum header file - outputFile = os.path.join(args.headers_dir, "locale", "locale.h") - os.makedirs(os.path.dirname(outputFile), exist_ok=True) - with open(outputFile, "w") as f: - f.write(headerLocale) - - return { - 'files': files - } - -def getLanguageVariableName(languageKey): - # Take the language key, prepend LANG_, uppercase, replace any non symbols - # with _ - key = languageKey.strip().upper() - key = re.sub(r'[^A-Z0-9]', '_', key) - return f"LANG_{key}" - -def processLanguage(asset): - cache = assetGetCache(asset['path']) - if cache is not None: - return cache - - # Load PO File - po = polib.pofile(asset['path']) - - langName = po.metadata.get('Language') - if langName not in LANGUAGE_DATA: - LANGUAGE_DATA[langName] = {} - - for entry in po: - key = entry.msgid - val = entry.msgstr - - if key not in LANGUAGE_KEYS: - LANGUAGE_KEYS.append(key) - - if key not in LANGUAGE_DATA[langName]: - LANGUAGE_DATA[langName][key] = val - else: - print(f"Error: Duplicate translation key '{key}' in language '{langName}'") - sys.exit(1) - - outLanguageData = { - 'data': po, - 'path': asset['path'], - 'files': [] - } - return assetCache(asset['path'], outLanguageData) \ No newline at end of file diff --git a/archive/asset/process/map.py b/archive/asset/process/map.py deleted file mode 100644 index f4384e96..00000000 --- a/archive/asset/process/map.py +++ /dev/null @@ -1,154 +0,0 @@ -import struct -import sys -import os -import json -from tools.asset.args import args -from tools.asset.cache import assetCache, assetGetCache -from tools.asset.path import getAssetRelativePath -from tools.dusk.defs import TILE_WIDTH, TILE_HEIGHT, TILE_DEPTH, CHUNK_WIDTH, CHUNK_HEIGHT, CHUNK_DEPTH, CHUNK_TILE_COUNT -from tools.dusk.map import Map -from tools.dusk.chunk import Chunk - -def convertModelData(modelData): - # TLDR; Model data stores things efficiently with indices, but we buffer it - # out to 6 vertex quads for simplicity. - outVertices = [] - outUVs = [] - outColors = [] - for indice in modelData['indices']: - vertex = modelData['vertices'][indice] - uv = modelData['uvs'][indice] - color = modelData['colors'][indice] - outVertices.append(vertex) - outUVs.append(uv) - outColors.append(color) - - return { - 'vertices': outVertices, - 'uvs': outUVs, - 'colors': outColors - } - -def processChunk(chunk): - cache = assetGetCache(chunk.getFilename()) - if cache: - return cache - - baseModel = { - 'vertices': [], - 'colors': [], - 'uvs': [] - } - models = [ baseModel ] - - for tileIndex, tile in chunk.tiles.items(): - tileBase = tile.getBaseTileModel() - - convertedBase = convertModelData(tileBase) - baseModel['vertices'].extend(convertedBase['vertices']) - baseModel['colors'].extend(convertedBase['colors']) - baseModel['uvs'].extend(convertedBase['uvs']) - - # Generate binary buffer for efficient output - buffer = bytearray() - buffer.extend(b'DMC')# Header - buffer.extend(len(chunk.tiles).to_bytes(4, 'little')) # Number of tiles - buffer.extend(len(models).to_bytes(1, 'little')) # Number of models - buffer.extend(len(chunk.entities).to_bytes(1, 'little')) # Number of entities - - # Buffer tile data as array of uint8_t - for tileIndex, tile in chunk.tiles.items(): - buffer.extend(tile.shape.to_bytes(1, 'little')) - - # # For each model - for model in models: - vertexCount = len(model['vertices']) - buffer.extend(vertexCount.to_bytes(4, 'little')) - for i in range(vertexCount): - vertex = model['vertices'][i] - uv = model['uvs'][i] - color = model['colors'][i] - - buffer.extend(color[0].to_bytes(1, 'little')) - buffer.extend(color[1].to_bytes(1, 'little')) - buffer.extend(color[2].to_bytes(1, 'little')) - buffer.extend(color[3].to_bytes(1, 'little')) - - buffer.extend(bytearray(struct.pack('header[0] != 'D' || - assetData->header[1] != 'P' || - assetData->header[2] != 'F' - ) { - errorThrow("Invalid palette header"); - } - - // Version (can only be 1 atm) - if(assetData->version != 0x01) { - errorThrow("Unsupported palette version"); - } - - // Check color count. - if( - assetData->colorCount == 0 || - assetData->colorCount > PALETTE_COLOR_COUNT_MAX - ) { - errorThrow("Invalid palette color count"); - } - - paletteInit( - palette, - assetData->colorCount, - assetData->colors - ); - errorOk(); -} \ No newline at end of file diff --git a/archive/assetpalette.h b/archive/assetpalette.h deleted file mode 100644 index cc328f2a..00000000 --- a/archive/assetpalette.h +++ /dev/null @@ -1,30 +0,0 @@ -/** - * Copyright (c) 2026 Dominic Masters - * - * This software is released under the MIT License. - * https://opensource.org/licenses/MIT - */ - -#pragma once -#include "error/error.h" -#include "display/texture/palette.h" - -typedef struct assetentire_s assetentire_t; - -#pragma pack(push, 1) -typedef struct { - char_t header[3]; - uint8_t version; - - uint8_t colorCount; - color_t colors[PALETTE_COLOR_COUNT_MAX]; -} assetpalette_t; -#pragma pack(pop) - -/** - * Loads a palette from the given data pointer into the output palette. - * - * @param entire Data received from the asset loader system. - * @return An error code. - */ -errorret_t assetPaletteLoad(assetentire_t entire); \ No newline at end of file diff --git a/archive/camera/CMakeLists.txt b/archive/camera/CMakeLists.txt deleted file mode 100644 index d8c90ab1..00000000 --- a/archive/camera/CMakeLists.txt +++ /dev/null @@ -1,10 +0,0 @@ -# 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 - camera.c -) \ No newline at end of file diff --git a/archive/camera/camera.c b/archive/camera/camera.c deleted file mode 100644 index 94379fa1..00000000 --- a/archive/camera/camera.c +++ /dev/null @@ -1,107 +0,0 @@ -/** - * Copyright (c) 2025 Dominic Masters - * - * This software is released under the MIT License. - * https://opensource.org/licenses/MIT - */ - -#include "camera.h" -#include "display/display.h" -#include "assert/assert.h" -#include "display/framebuffer/framebuffer.h" -#include "display/screen/screen.h" - -void cameraInit(camera_t *camera) { - cameraInitPerspective(camera); -} - -void cameraInitPerspective(camera_t *camera) { - assertNotNull(camera, "Not a camera component"); - - camera->projType = CAMERA_PROJECTION_TYPE_PERSPECTIVE; - camera->perspective.fov = glm_rad(45.0f); - camera->nearClip = 0.1f; - camera->farClip = 10000.0f; - - camera->viewType = CAMERA_VIEW_TYPE_LOOKAT; - glm_vec3_copy((vec3){ 5.0f, 5.0f, 5.0f }, camera->lookat.position); - glm_vec3_copy((vec3){ 0.0f, 1.0f, 0.0f }, camera->lookat.up); - glm_vec3_copy((vec3){ 0.0f, 0.0f, 0.0f }, camera->lookat.target); -} - -void cameraInitOrthographic(camera_t *camera) { - assertNotNull(camera, "Not a camera component"); - - camera->projType = CAMERA_PROJECTION_TYPE_ORTHOGRAPHIC; - camera->orthographic.left = 0.0f; - camera->orthographic.right = SCREEN.width; - camera->orthographic.top = SCREEN.height; - camera->orthographic.bottom = 0.0f; - camera->nearClip = 0.1f; - camera->farClip = 1.0f; - - camera->viewType = CAMERA_VIEW_TYPE_2D; - glm_vec2_copy((vec2){ 0.0f, 0.0f }, camera->_2d.position); - camera->_2d.zoom = 1.0f; -} - -void cameraGetProjectionMatrix(camera_t *camera, mat4 dest) { - assertNotNull(camera, "Not a camera component"); - assertNotNull(dest, "Destination matrix must not be null"); - - if( - camera->projType == CAMERA_PROJECTION_TYPE_PERSPECTIVE || - camera->projType == CAMERA_PROJECTION_TYPE_PERSPECTIVE_FLIPPED - ) { - glm_mat4_identity(dest); - glm_perspective( - camera->perspective.fov, - SCREEN.aspect, - camera->nearClip, - camera->farClip, - dest - ); - - if(camera->projType == CAMERA_PROJECTION_TYPE_PERSPECTIVE_FLIPPED) { - dest[1][1] *= -1.0f; - } - } else if(camera->projType == CAMERA_PROJECTION_TYPE_ORTHOGRAPHIC) { - glm_mat4_identity(dest); - glm_ortho( - camera->orthographic.left, - camera->orthographic.right, - camera->orthographic.top, - camera->orthographic.bottom, - camera->nearClip, - camera->farClip, - dest - ); - } -} - -void cameraGetViewMatrix(camera_t *camera, mat4 dest) { - assertNotNull(camera, "Not a camera component"); - assertNotNull(dest, "Destination matrix must not be null"); - - if(camera->viewType == CAMERA_VIEW_TYPE_MATRIX) { - glm_mat4_ucopy(camera->view, dest); - } else if(camera->viewType == CAMERA_VIEW_TYPE_LOOKAT) { - glm_mat4_identity(dest); - glm_lookat( - camera->lookat.position, - camera->lookat.target, - camera->lookat.up, - dest - ); - } else if(camera->viewType == CAMERA_VIEW_TYPE_2D) { - glm_mat4_identity(dest); - glm_lookat( - (vec3){ camera->_2d.position[0], camera->_2d.position[1], 0.5f }, - (vec3){ camera->_2d.position[0], camera->_2d.position[1], 0.0f }, - (vec3){ 0.0f, 1.0f, 0.0f }, - dest - ); - } else if(camera->viewType == CAMERA_VIEW_TYPE_LOOKAT_PIXEL_PERFECT) { - assertUnreachable("LOOKAT_PIXEL_PERFECT view type is not implemented yet"); - } -} \ No newline at end of file diff --git a/archive/camera/camera.h b/archive/camera/camera.h deleted file mode 100644 index a5ff7c23..00000000 --- a/archive/camera/camera.h +++ /dev/null @@ -1,97 +0,0 @@ -/** - * Copyright (c) 2025 Dominic Masters - * - * This software is released under the MIT License. - * https://opensource.org/licenses/MIT - */ - -#pragma once -#include "dusk.h" -#include "display/color.h" -#include "display/camera/cameraplatform.h" - -#ifndef cameraPushMatrixPlatform - #error "cameraPushMatrixPlatform must be defined" -#endif - - -typedef enum { - CAMERA_VIEW_TYPE_MATRIX, - CAMERA_VIEW_TYPE_LOOKAT, - CAMERA_VIEW_TYPE_2D, - CAMERA_VIEW_TYPE_LOOKAT_PIXEL_PERFECT -} cameraviewtype_t; - -typedef struct camera_s { - union { - mat4 view; - - struct { - vec3 position; - vec3 target; - vec3 up; - } lookat; - - struct { - vec3 offset; - vec3 target; - vec3 up; - float_t pixelsPerUnit; - } lookatPixelPerfect; - - struct { - vec2 position; - float_t zoom; - } _2d; - }; - - union { - struct { - float_t fov; - } perspective; - - struct { - float_t left; - float_t right; - float_t top; - float_t bottom; - } orthographic; - }; - - float_t nearClip; - float_t farClip; - - cameraprojectiontype_t projType; - cameraviewtype_t viewType; -} camera_t; - -/** - * Initializes a camera to default values. This calls cameraInitPerspective. - */ -void cameraInit(camera_t *camera); - -/** - * Initializes a camera for perspective projection. - */ -void cameraInitPerspective(camera_t *camera); - -/** - * Initializes a camera for orthographic projection. - */ -void cameraInitOrthographic(camera_t *camera); - -/** - * Gets the projection matrix for a camera. - * - * @param camera Camera to get the projection matrix for - * @param dest Matrix to store the projection matrix in - */ -void cameraGetProjectionMatrix(camera_t *camera, mat4 dest); - -/** - * Gets the view matrix for a camera. - * - * @param camera Camera to get the view matrix for - * @param dest Matrix to store the view matrix in - */ -void cameraGetViewMatrix(camera_t *camera, mat4 dest); \ No newline at end of file diff --git a/archive/dusk/chunk.py b/archive/dusk/chunk.py deleted file mode 100644 index b15b917a..00000000 --- a/archive/dusk/chunk.py +++ /dev/null @@ -1,158 +0,0 @@ -import json -import os -from tools.dusk.event import Event -from tools.dusk.defs import CHUNK_WIDTH, CHUNK_HEIGHT, CHUNK_DEPTH, CHUNK_VERTEX_COUNT_MAX, TILE_SHAPE_NULL -from tools.dusk.tile import Tile -from tools.dusk.entity import Entity -from tools.dusk.region import Region -from tools.editor.map.vertexbuffer import VertexBuffer -from OpenGL.GL import * - -class Chunk: - def __init__(self, map, x, y, z): - self.map = map - self.x = x - self.y = y - self.z = z - self.current = {} - self.original = {} - self.entities = {} - self.regions = {} - self.onChunkData = Event() - self.dirty = False - - self.tiles = {} - self.vertexBuffer = VertexBuffer() - - # Test Region - region = self.regions[0] = Region(self) - region.minX = 0 - region.minY = 0 - region.minZ = 0 - region.maxX = 32 - region.maxY = 32 - region.maxZ = 32 - region.updateVertexs() - - # Gen tiles. - tileIndex = 0 - for tz in range(CHUNK_DEPTH): - for ty in range(CHUNK_HEIGHT): - for tx in range(CHUNK_WIDTH): - self.tiles[tileIndex] = Tile(self, tx, ty, tz, tileIndex) - tileIndex += 1 - - # Update vertices - self.tileUpdateVertices() - - def reload(self, newX, newY, newZ): - self.x = newX - self.y = newY - self.z = newZ - self.entities = {} - for tile in self.tiles.values(): - tile.chunkReload(newX, newY, newZ) - self.load() - - def tileUpdateVertices(self): - self.vertexBuffer.clear() - for tile in self.tiles.values(): - tile.buffer(self.vertexBuffer) - self.vertexBuffer.buildData() - - def load(self): - fname = self.getFilename() - if not fname or not os.path.exists(fname): - self.new() - return - try: - with open(fname, 'r') as f: - data = json.load(f) - - if not 'shapes' in data: - data['shapes'] = [] - - # For each tile. - for tile in self.tiles.values(): - tile.load(data) - - # For each entity. - self.entities = {} - if 'entities' in data: - for id, entData in enumerate(data['entities']): - ent = Entity(self) - ent.load(entData) - self.entities[id] = ent - - self.tileUpdateVertices() - self.dirty = False - self.onChunkData.invoke(self) - self.map.onEntityData.invoke() - except Exception as e: - raise RuntimeError(f"Failed to load chunk file: {e}") - - def save(self): - if not self.isDirty(): - return - - dataOut = { - 'shapes': [], - 'entities': [] - } - - for tile in self.tiles.values(): - dataOut['shapes'].append(tile.shape) - - for ent in self.entities.values(): - entData = {} - ent.save(entData) - dataOut['entities'].append(entData) - - fname = self.getFilename() - if not fname: - raise ValueError("No filename specified for saving chunk.") - try: - with open(fname, 'w') as f: - json.dump(dataOut, f) - self.dirty = False - self.onChunkData.invoke(self) - except Exception as e: - raise RuntimeError(f"Failed to save chunk file: {e}") - - def new(self): - for tile in self.tiles.values(): - tile.shape = TILE_SHAPE_NULL - - self.tileUpdateVertices() - self.dirty = False - self.onChunkData.invoke(self) - - def isDirty(self): - return self.dirty - - def getFilename(self): - if not self.map or not hasattr(self.map, 'getChunkDirectory'): - return None - dirPath = self.map.getChunkDirectory() - if dirPath is None: - return None - return f"{dirPath}/{self.x}_{self.y}_{self.z}.json" - - def draw(self): - self.vertexBuffer.draw() - - def addEntity(self, localX=0, localY=0, localZ=0): - ent = Entity(self, localX, localY, localZ) - self.entities[len(self.entities)] = ent - self.map.onEntityData.invoke() - self.dirty = True - return ent - - def removeEntity(self, entity): - for key, val in list(self.entities.items()): - if val == entity: - del self.entities[key] - self.map.onEntityData.invoke() - self.dirty = True - return True - return False \ No newline at end of file diff --git a/archive/dusk/defs.py b/archive/dusk/defs.py deleted file mode 100644 index 5f5c7952..00000000 --- a/archive/dusk/defs.py +++ /dev/null @@ -1,49 +0,0 @@ -from dotenv import load_dotenv, dotenv_values -import os -import sys - -current_file_path = os.path.abspath(__file__) -duskDefsPath = os.path.join(os.path.dirname(current_file_path), "..", "..", "src", "duskdefs.env") - -# Ensure the .env file exists -if not os.path.isfile(duskDefsPath): - print(f"Error: .env file not found at {duskDefsPath}") - sys.exit(1) - -load_dotenv(dotenv_path=duskDefsPath) -defs = {key: os.getenv(key) for key in os.environ.keys()} - -fileDefs = dotenv_values(dotenv_path=duskDefsPath) - -# Parsed out definitions -CHUNK_WIDTH = int(defs.get('CHUNK_WIDTH')) -CHUNK_HEIGHT = int(defs.get('CHUNK_HEIGHT')) -CHUNK_DEPTH = int(defs.get('CHUNK_DEPTH')) -CHUNK_TILE_COUNT = CHUNK_WIDTH * CHUNK_HEIGHT * CHUNK_DEPTH -CHUNK_VERTEX_COUNT_MAX = int(defs.get('CHUNK_VERTEX_COUNT_MAX')) - -TILE_WIDTH = float(defs.get('TILE_WIDTH')) -TILE_HEIGHT = float(defs.get('TILE_HEIGHT')) -TILE_DEPTH = float(defs.get('TILE_DEPTH')) - -RPG_CAMERA_PIXELS_PER_UNIT = float(defs.get('RPG_CAMERA_PIXELS_PER_UNIT')) -RPG_CAMERA_Z_OFFSET = float(defs.get('RPG_CAMERA_Z_OFFSET')) -RPG_CAMERA_FOV = float(defs.get('RPG_CAMERA_FOV')) - -MAP_WIDTH = 5 -MAP_HEIGHT = 5 -MAP_DEPTH = 3 -MAP_CHUNK_COUNT = MAP_WIDTH * MAP_HEIGHT * MAP_DEPTH - -TILE_SHAPES = {} -for key in defs.keys(): - if key.startswith('TILE_SHAPE_'): - globals()[key] = int(defs.get(key)) - TILE_SHAPES[key] = int(defs.get(key)) - -ENTITY_TYPES = {} -for key in defs.keys(): - if key.startswith('ENTITY_TYPE_'): - globals()[key] = int(defs.get(key)) - if key != 'ENTITY_TYPE_COUNT': - ENTITY_TYPES[key] = int(defs.get(key)) \ No newline at end of file diff --git a/archive/dusk/entity.py b/archive/dusk/entity.py deleted file mode 100644 index 221b28fc..00000000 --- a/archive/dusk/entity.py +++ /dev/null @@ -1,90 +0,0 @@ -from tools.dusk.defs import ENTITY_TYPE_NULL, ENTITY_TYPE_NPC, CHUNK_WIDTH, CHUNK_HEIGHT, CHUNK_DEPTH, TILE_WIDTH, TILE_HEIGHT, TILE_DEPTH -from tools.editor.map.vertexbuffer import VertexBuffer - -class Entity: - def __init__(self, chunk, localX=0, localY=0, localZ=0): - self.type = ENTITY_TYPE_NPC - self.name = "Unititled" - self.localX = localX % CHUNK_WIDTH - self.localY = localY % CHUNK_HEIGHT - self.localZ = localZ % CHUNK_DEPTH - - self.chunk = chunk - self.vertexBuffer = VertexBuffer() - pass - - def load(self, obj): - self.type = obj.get('type', ENTITY_TYPE_NULL) - self.localX = obj.get('x', 0) - self.localY = obj.get('y', 0) - self.localZ = obj.get('z', 0) - self.name = obj.get('name', "Untitled") - pass - - def save(self, obj): - obj['type'] = self.type - obj['name'] = self.name - obj['x'] = self.localX - obj['y'] = self.localY - obj['z'] = self.localZ - pass - - def setType(self, entityType): - if self.type == entityType: - return - self.type = entityType - self.chunk.dirty = True - self.chunk.map.onEntityData.invoke() - - def setName(self, name): - if self.name == name: - return - self.name = name - self.chunk.dirty = True - self.chunk.map.onEntityData.invoke() - - def draw(self): - self.vertexBuffer.clear() - - startX = (self.chunk.x * CHUNK_WIDTH + self.localX) * TILE_WIDTH - startY = (self.chunk.y * CHUNK_HEIGHT + self.localY) * TILE_HEIGHT - startZ = (self.chunk.z * CHUNK_DEPTH + self.localZ) * TILE_DEPTH - w = TILE_WIDTH - h = TILE_HEIGHT - d = TILE_DEPTH - - # Center - startX -= w / 2 - startY -= h / 2 - startZ -= d / 2 - - # Offset upwards a little - startZ += 1 - - # Buffer simple quad at current position (need 6 positions) - self.vertexBuffer.vertices = [ - startX, startY, startZ, - startX + w, startY, startZ, - startX + w, startY + h, startZ, - startX, startY, startZ, - startX + w, startY + h, startZ, - startX, startY + h, startZ, - ] - self.vertexBuffer.colors = [ - 1.0, 0.0, 1.0, 1.0, - 1.0, 0.0, 1.0, 1.0, - 1.0, 0.0, 1.0, 1.0, - 1.0, 0.0, 1.0, 1.0, - 1.0, 0.0, 1.0, 1.0, - 1.0, 0.0, 1.0, 1.0, - ] - self.vertexBuffer.uvs = [ - 0.0, 0.0, - 1.0, 0.0, - 1.0, 1.0, - 0.0, 0.0, - 1.0, 1.0, - 0.0, 1.0, - ] - self.vertexBuffer.buildData() - self.vertexBuffer.draw() \ No newline at end of file diff --git a/archive/dusk/event.py b/archive/dusk/event.py deleted file mode 100644 index d98eed80..00000000 --- a/archive/dusk/event.py +++ /dev/null @@ -1,18 +0,0 @@ -class Event: - def __init__(self): - self._subscribers = [] - - def sub(self, callback): - """Subscribe a callback to the event.""" - if callback not in self._subscribers: - self._subscribers.append(callback) - - def unsub(self, callback): - """Unsubscribe a callback from the event.""" - if callback in self._subscribers: - self._subscribers.remove(callback) - - def invoke(self, *args, **kwargs): - """Invoke all subscribers with the given arguments.""" - for callback in self._subscribers: - callback(*args, **kwargs) diff --git a/archive/dusk/map.py b/archive/dusk/map.py deleted file mode 100644 index 4b2ebbf0..00000000 --- a/archive/dusk/map.py +++ /dev/null @@ -1,259 +0,0 @@ -import json -import sys -from tools.dusk.event import Event -from PyQt5.QtWidgets import QFileDialog, QMessageBox -from PyQt5.QtCore import QTimer -import os -from tools.dusk.chunk import Chunk -from tools.dusk.defs import MAP_WIDTH, MAP_HEIGHT, MAP_DEPTH, CHUNK_WIDTH, CHUNK_HEIGHT, CHUNK_DEPTH -import traceback - -MAP_DEFAULT_PATH = os.path.join(os.path.dirname(__file__), '../../assets/map/') -EDITOR_CONFIG_PATH = os.path.join(os.path.dirname(__file__), '.editor') - -class Map: - def __init__(self, parent): - self.parent = parent - self.data = {} - self.dataOriginal = {} - self.position = [None, None, None] # x, y, z - self.topLeftX = None - self.topLeftY = None - self.topLeftZ = None - self.chunks = {} - self.onMapData = Event() - self.onPositionChange = Event() - self.onEntityData = Event() - self.mapFileName = None - self.lastFile = None - self.firstLoad = True - - index = 0 - for x in range(MAP_WIDTH): - for y in range(MAP_HEIGHT): - for z in range(MAP_DEPTH): - self.chunks[index] = Chunk(self, x, y, z) - index += 1 - - # Only in editor instances: - self.moveTo(0, 0, 0) - if parent is not None: - QTimer.singleShot(16, self.loadLastFile) - - def loadLastFile(self): - if not os.path.exists(EDITOR_CONFIG_PATH): - return - try: - with open(EDITOR_CONFIG_PATH, 'r') as f: - config = json.load(f) - lastFile = config.get('lastFile') - lastPosition = config.get('lastPosition') - leftPanelIndex = config.get('leftPanelIndex') - if lastFile and os.path.exists(lastFile): - self.load(lastFile) - if lastPosition and isinstance(lastPosition, list) and len(lastPosition) == 3: - self.moveTo(*lastPosition) - if leftPanelIndex is not None: - self.parent.leftPanel.tabs.setCurrentIndex(leftPanelIndex) - except Exception: - traceback.print_exc() - - def updateEditorConfig(self): - if self.parent is None: - return - try: - mapFileName = self.getMapFilename() - config = { - 'lastFile': mapFileName if mapFileName else "", - 'lastPosition': self.position, - 'leftPanelIndex': self.parent.leftPanel.tabs.currentIndex() - } - config_dir = os.path.dirname(EDITOR_CONFIG_PATH) - if not os.path.exists(config_dir): - os.makedirs(config_dir, exist_ok=True) - with open(EDITOR_CONFIG_PATH, 'w') as f: - json.dump(config, f, indent=2) - except Exception: - traceback.print_exc() - - def newFile(self): - self.data = {} - self.dataOriginal = {} - self.mapFileName = None - self.lastFile = None - for chunk in self.chunks.values(): - chunk.new() - self.moveTo(0, 0, 0) - self.onMapData.invoke(self.data) - self.updateEditorConfig() - - def save(self, fname=None): - if not self.getMapFilename() and fname is None: - filePath, _ = QFileDialog.getSaveFileName(None, "Save Map File", MAP_DEFAULT_PATH, "Map Files (*.json)") - if not filePath: - return - self.mapFileName = filePath - if fname: - self.mapFileName = fname - try: - with open(self.getMapFilename(), 'w') as f: - json.dump(self.data, f, indent=2) - self.dataOriginal = json.loads(json.dumps(self.data)) # Deep copy - for chunk in self.chunks.values(): - chunk.save() - self.updateEditorConfig() - except Exception as e: - traceback.print_exc() - QMessageBox.critical(None, "Save Error", f"Failed to save map file:\n{e}") - - def load(self, fileName): - try: - with open(fileName, 'r') as f: - self.data = json.load(f) - self.mapFileName = fileName - self.dataOriginal = json.loads(json.dumps(self.data)) # Deep copy - for chunk in self.chunks.values(): - chunk.load() - self.onMapData.invoke(self.data) - self.updateEditorConfig() - except Exception as e: - traceback.print_exc() - QMessageBox.critical(None, "Load Error", f"Failed to load map file:\n{e}") - - def isMapFileDirty(self): - return json.dumps(self.data, sort_keys=True) != json.dumps(self.dataOriginal, sort_keys=True) - - def isDirty(self): - return self.isMapFileDirty() or self.anyChunksDirty() - - def getMapFilename(self): - return self.mapFileName if self.mapFileName and os.path.exists(self.mapFileName) else None - - def getMapDirectory(self): - if self.mapFileName is None: - return None - dirname = os.path.dirname(self.mapFileName) - return dirname - - def getChunkDirectory(self): - dirName = self.getMapDirectory() - if dirName is None: - return None - return os.path.join(dirName, 'chunks') - - def anyChunksDirty(self): - for chunk in self.chunks.values(): - if chunk.isDirty(): - return True - return False - - def moveTo(self, x, y, z): - if self.position == [x, y, z]: - return - - # We need to decide if the chunks should be unloaded here or not. - newTopLeftChunkX = x // CHUNK_WIDTH - (MAP_WIDTH // 2) - newTopLeftChunkY = y // CHUNK_HEIGHT - (MAP_HEIGHT // 2) - newTopLeftChunkZ = z // CHUNK_DEPTH - (MAP_DEPTH // 2) - - if(newTopLeftChunkX != self.topLeftX or - newTopLeftChunkY != self.topLeftY or - newTopLeftChunkZ != self.topLeftZ): - - chunksToUnload = [] - chunksToKeep = [] - for chunk in self.chunks.values(): - chunkWorldX = chunk.x - chunkWorldY = chunk.y - chunkWorldZ = chunk.z - if(chunkWorldX < newTopLeftChunkX or - chunkWorldX >= newTopLeftChunkX + MAP_WIDTH or - chunkWorldY < newTopLeftChunkY or - chunkWorldY >= newTopLeftChunkY + MAP_HEIGHT or - chunkWorldZ < newTopLeftChunkZ or - chunkWorldZ >= newTopLeftChunkZ + MAP_DEPTH): - chunksToUnload.append(chunk) - else: - chunksToKeep.append(chunk) - - # Unload chunks that are out of the new bounds. - for chunk in chunksToUnload: - if chunk.isDirty(): - print(f"Can't move map, some chunks are dirty: ({chunk.x}, {chunk.y}, {chunk.z})") - return - - # Now we can safely unload the chunks. - chunkIndex = 0 - newChunks = {} - for chunk in chunksToKeep: - newChunks[chunkIndex] = chunk - chunkIndex += 1 - - for xPos in range(newTopLeftChunkX, newTopLeftChunkX + MAP_WIDTH): - for yPos in range(newTopLeftChunkY, newTopLeftChunkY + MAP_HEIGHT): - for zPos in range(newTopLeftChunkZ, newTopLeftChunkZ + MAP_DEPTH): - # Check if we already have this chunk. - found = False - for chunk in chunksToKeep: - if chunk.x == xPos and chunk.y == yPos and chunk.z == zPos: - found = True - break - if not found: - # Create a new chunk. - newChunk = chunksToUnload.pop() - newChunk.reload(xPos, yPos, zPos) - newChunks[chunkIndex] = newChunk - chunkIndex += 1 - - self.chunks = newChunks - self.topLeftX = newTopLeftChunkX - self.topLeftY = newTopLeftChunkY - self.topLeftZ = newTopLeftChunkZ - - self.position = [x, y, z] - self.onPositionChange.invoke(self.position) - if not self.firstLoad: - self.updateEditorConfig() - self.firstLoad = False - - def moveRelative(self, x, y, z): - self.moveTo( - self.position[0] + x, - self.position[1] + y, - self.position[2] + z - ) - - def draw(self): - for chunk in self.chunks.values(): - chunk.draw() - - for chunk in self.chunks.values(): - for entity in chunk.entities.values(): - entity.draw() - - # Only render on Region tab - if self.parent.leftPanel.tabs.currentWidget() == self.parent.leftPanel.regionPanel: - for chunk in self.chunks.values(): - for region in chunk.regions.values(): - region.draw() - - def getChunkAtWorldPos(self, x, y, z): - chunkX = x // CHUNK_WIDTH - chunkY = y // CHUNK_HEIGHT - chunkZ = z // CHUNK_DEPTH - for chunk in self.chunks.values(): - if chunk.x == chunkX and chunk.y == chunkY and chunk.z == chunkZ: - return chunk - return None - - def getTileAtWorldPos(self, x, y, z): - chunk = self.getChunkAtWorldPos(x, y, z) - if not chunk: - print("No chunk found at position:", (x, y, z)) - return None - - tileX = x % CHUNK_WIDTH - tileY = y % CHUNK_HEIGHT - tileZ = z % CHUNK_DEPTH - tileIndex = tileX + tileY * CHUNK_WIDTH + tileZ * CHUNK_WIDTH * CHUNK_HEIGHT - return chunk.tiles.get(tileIndex) \ No newline at end of file diff --git a/archive/dusk/region.py b/archive/dusk/region.py deleted file mode 100644 index 4737afcf..00000000 --- a/archive/dusk/region.py +++ /dev/null @@ -1,141 +0,0 @@ -from tools.dusk.defs import CHUNK_WIDTH, CHUNK_HEIGHT, CHUNK_DEPTH, TILE_WIDTH, TILE_HEIGHT, TILE_DEPTH -from tools.editor.map.vertexbuffer import VertexBuffer -from OpenGL.GL import * -from OpenGL.GLU import * - -class Region: - def __init__(self, chunk): - self.minX = 0 - self.minY = 0 - self.minZ = 0 - self.maxX = 0 - self.maxY = 0 - self.maxZ = 0 - self.chunk = chunk - self.vertexBuffer = VertexBuffer() - self.color = (1.0, 0.0, 0.0) - self.updateVertexs() - pass - - def updateVertexs(self): - # Draw a quad, semi transparent with solid outlines - vminX = (self.minX * CHUNK_WIDTH) * TILE_WIDTH - vminY = (self.minY * CHUNK_HEIGHT) * TILE_HEIGHT - vminZ = (self.minZ * CHUNK_DEPTH) * TILE_DEPTH - vmaxX = (self.maxX * CHUNK_WIDTH) * TILE_WIDTH - vmaxY = (self.maxY * CHUNK_HEIGHT) * TILE_HEIGHT - vmaxZ = (self.maxZ * CHUNK_DEPTH) * TILE_DEPTH - alpha = 0.25 - - # Move back half a tile width - vminX -= TILE_WIDTH / 2 - vmaxX -= TILE_WIDTH / 2 - vminY -= TILE_HEIGHT / 2 - vmaxY -= TILE_HEIGHT / 2 - vminZ -= TILE_DEPTH / 2 - vmaxZ -= TILE_DEPTH / 2 - - # Cube (6 verts per face) - self.vertexBuffer.vertices = [ - # Front face - vminX, vminY, vmaxZ, - vmaxX, vminY, vmaxZ, - vmaxX, vmaxY, vmaxZ, - vminX, vminY, vmaxZ, - vmaxX, vmaxY, vmaxZ, - vminX, vmaxY, vmaxZ, - - # Back face - vmaxX, vminY, vminZ, - vminX, vminY, vminZ, - vminX, vmaxY, vminZ, - vmaxX, vminY, vminZ, - vminX, vmaxY, vminZ, - vmaxX, vmaxY, vminZ, - - # Left face - vminX, vminY, vminZ, - vminX, vminY, vmaxZ, - vminX, vmaxY, vmaxZ, - vminX, vminY, vminZ, - vminX, vmaxY, vmaxZ, - vminX, vmaxY, vminZ, - - # Right face - vmaxX, vminY, vmaxZ, - vmaxX, vminY, vminZ, - vmaxX, vmaxY, vminZ, - vmaxX, vminY, vmaxZ, - vmaxX, vmaxY, vminZ, - vmaxX, vmaxY, vmaxZ, - - # Top face - vminX, vmaxY, vmaxZ, - vmaxX, vmaxY, vmaxZ, - vmaxX, vmaxY, vminZ, - vminX, vmaxY, vmaxZ, - vmaxX, vmaxY, vminZ, - vminX, vmaxY, vminZ, - - # Bottom face - vminX, vminY, vminZ, - vmaxX, vminY, vminZ, - vmaxX, vminY, vmaxZ, - vminX, vminY, vminZ, - vmaxX, vminY, vmaxZ, - vminX, vminY, vmaxZ, - ] - - self.vertexBuffer.colors = [ - # Front face - self.color[0], self.color[1], self.color[2], alpha, - self.color[0], self.color[1], self.color[2], alpha, - self.color[0], self.color[1], self.color[2], alpha, - self.color[0], self.color[1], self.color[2], alpha, - self.color[0], self.color[1], self.color[2], alpha, - self.color[0], self.color[1], self.color[2], alpha, - - # Back face - self.color[0], self.color[1], self.color[2], alpha, - self.color[0], self.color[1], self.color[2], alpha, - self.color[0], self.color[1], self.color[2], alpha, - self.color[0], self.color[1], self.color[2], alpha, - self.color[0], self.color[1], self.color[2], alpha, - self.color[0], self.color[1], self.color[2], alpha, - - # Left face - self.color[0], self.color[1], self.color[2], alpha, - self.color[0], self.color[1], self.color[2], alpha, - self.color[0], self.color[1], self.color[2], alpha, - self.color[0], self.color[1], self.color[2], alpha, - self.color[0], self.color[1], self.color[2], alpha, - self.color[0], self.color[1], self.color[2], alpha, - - # Right face - self.color[0], self.color[1], self.color[2], alpha, - self.color[0], self.color[1], self.color[2], alpha, - self.color[0], self.color[1], self.color[2], alpha, - self.color[0], self.color[1], self.color[2], alpha, - self.color[0], self.color[1], self.color[2], alpha, - self.color[0], self.color[1], self.color[2], alpha, - - # Top face - self.color[0], self.color[1], self.color[2], alpha, - self.color[0], self.color[1], self.color[2], alpha, - self.color[0], self.color[1], self.color[2], alpha, - self.color[0], self.color[1], self.color[2], alpha, - self.color[0], self.color[1], self.color[2], alpha, - self.color[0], self.color[1], self.color[2], alpha, - - # Bottom face - self.color[0], self.color[1], self.color[2], alpha, - self.color[0], self.color[1], self.color[2], alpha, - self.color[0], self.color[1], self.color[2], alpha, - self.color[0], self.color[1], self.color[2], alpha, - self.color[0], self.color[1], self.color[2], alpha, - self.color[0], self.color[1], self.color[2], alpha, - ] - self.vertexBuffer.buildData() - - def draw(self): - self.vertexBuffer.draw() \ No newline at end of file diff --git a/archive/dusk/tile.py b/archive/dusk/tile.py deleted file mode 100644 index 8f91a335..00000000 --- a/archive/dusk/tile.py +++ /dev/null @@ -1,206 +0,0 @@ -from OpenGL.GL import * -from tools.dusk.defs import ( - TILE_WIDTH, TILE_HEIGHT, TILE_DEPTH, - CHUNK_WIDTH, CHUNK_HEIGHT, CHUNK_DEPTH, - TILE_SHAPE_NULL, TILE_SHAPE_FLOOR, - TILE_SHAPE_RAMP_NORTH, TILE_SHAPE_RAMP_SOUTH, - TILE_SHAPE_RAMP_EAST, TILE_SHAPE_RAMP_WEST, - TILE_SHAPE_RAMP_SOUTHWEST, TILE_SHAPE_RAMP_SOUTHEAST, - TILE_SHAPE_RAMP_NORTHWEST, TILE_SHAPE_RAMP_NORTHEAST -) - -def getItem(arr, index, default): - if index < len(arr): - return arr[index] - return default - -class Tile: - def __init__(self, chunk, x, y, z, tileIndex): - self.shape = TILE_SHAPE_NULL - - self.chunk = chunk - self.x = x - self.y = y - self.z = z - self.index = tileIndex - - self.posX = x * TILE_WIDTH + chunk.x * CHUNK_WIDTH * TILE_WIDTH - self.posY = y * TILE_HEIGHT + chunk.y * CHUNK_HEIGHT * TILE_HEIGHT - self.posZ = z * TILE_DEPTH + chunk.z * CHUNK_DEPTH * TILE_DEPTH - - def chunkReload(self, newX, newY, newZ): - self.posX = self.x * TILE_WIDTH + newX * CHUNK_WIDTH * TILE_WIDTH - self.posY = self.y * TILE_HEIGHT + newY * CHUNK_HEIGHT * TILE_HEIGHT - self.posZ = self.z * TILE_DEPTH + newZ * CHUNK_DEPTH * TILE_DEPTH - - def load(self, chunkData): - self.shape = getItem(chunkData['shapes'], self.index, TILE_SHAPE_NULL) - - def setShape(self, shape): - if shape == self.shape: - return - - self.shape = shape - self.chunk.dirty = True - self.chunk.tileUpdateVertices() - self.chunk.onChunkData.invoke(self.chunk) - - def getBaseTileModel(self): - vertices = [] - indices = [] - uvs = [] - colors = [] - - if self.shape == TILE_SHAPE_NULL: - pass - - elif self.shape == TILE_SHAPE_FLOOR: - vertices = [ - (self.posX, self.posY, self.posZ), - (self.posX + TILE_WIDTH, self.posY, self.posZ), - (self.posX + TILE_WIDTH, self.posY + TILE_HEIGHT, self.posZ), - (self.posX, self.posY + TILE_HEIGHT, self.posZ) - ] - indices = [0, 1, 2, 0, 2, 3] - uvs = [ (0, 0), (1, 0), (1, 1), (0, 1) ] - colors = [ (255, 255, 255, 255) ] * 4 - - elif self.shape == TILE_SHAPE_RAMP_NORTH: - vertices = [ - (self.posX, self.posY, self.posZ + TILE_DEPTH), - (self.posX + TILE_WIDTH, self.posY, self.posZ + TILE_DEPTH), - (self.posX + TILE_WIDTH, self.posY + TILE_HEIGHT, self.posZ), - (self.posX, self.posY + TILE_HEIGHT, self.posZ) - ] - indices = [0, 1, 2, 0, 2, 3] - uvs = [ (0, 0), (1, 0), (1, 1), (0, 1) ] - colors = [ (255, 0, 0, 255) ] * 4 - - elif self.shape == TILE_SHAPE_RAMP_SOUTH: - vertices = [ - (self.posX, self.posY, self.posZ), - (self.posX + TILE_WIDTH, self.posY, self.posZ), - (self.posX + TILE_WIDTH, self.posY + TILE_HEIGHT, self.posZ + TILE_DEPTH), - (self.posX, self.posY + TILE_HEIGHT, self.posZ + TILE_DEPTH) - ] - indices = [0, 1, 2, 0, 2, 3] - uvs = [ (0, 0), (1, 0), (1, 1), (0, 1) ] - colors = [ (0, 255, 0, 255) ] * 4 - - elif self.shape == TILE_SHAPE_RAMP_EAST: - vertices = [ - (self.posX, self.posY, self.posZ), - (self.posX + TILE_WIDTH, self.posY, self.posZ + TILE_DEPTH), - (self.posX + TILE_WIDTH, self.posY + TILE_HEIGHT, self.posZ + TILE_DEPTH), - (self.posX, self.posY + TILE_HEIGHT, self.posZ) - ] - indices = [0, 1, 2, 0, 2, 3] - uvs = [ (0, 0), (1, 0), (1, 1), (0, 1) ] - colors = [ (0, 0, 255, 255) ] * 4 - - elif self.shape == TILE_SHAPE_RAMP_WEST: - vertices = [ - (self.posX, self.posY, self.posZ + TILE_DEPTH), - (self.posX + TILE_WIDTH, self.posY, self.posZ), - (self.posX + TILE_WIDTH, self.posY + TILE_HEIGHT, self.posZ), - (self.posX, self.posY + TILE_HEIGHT, self.posZ + TILE_DEPTH) - ] - indices = [0, 1, 2, 0, 2, 3] - uvs = [ (0, 0), (1, 0), (1, 1), (0, 1) ] - colors = [ (255, 255, 0, 255) ] * 4 - - elif self.shape == TILE_SHAPE_RAMP_SOUTHWEST: - vertices = [ - (self.posX, self.posY, self.posZ + TILE_DEPTH), - (self.posX + TILE_WIDTH, self.posY, self.posZ), - (self.posX + TILE_WIDTH, self.posY + TILE_HEIGHT, self.posZ + TILE_DEPTH), - (self.posX, self.posY + TILE_HEIGHT, self.posZ + TILE_DEPTH) - ] - indices = [0, 1, 2, 0, 2, 3] - uvs = [ (0, 0), (1, 0), (1, 1), (0, 1) ] - colors = [ (255, 128, 0, 255) ] * 4 - - elif self.shape == TILE_SHAPE_RAMP_NORTHWEST: - vertices = [ - (self.posX, self.posY, self.posZ + TILE_DEPTH), - (self.posX + TILE_WIDTH, self.posY, self.posZ + TILE_DEPTH), - (self.posX + TILE_WIDTH, self.posY + TILE_HEIGHT, self.posZ), - (self.posX, self.posY + TILE_HEIGHT, self.posZ + TILE_DEPTH) - ] - indices = [0, 1, 2, 0, 2, 3] - uvs = [ (0, 0), (1, 0), (1, 1), (0, 1) ] - colors = [ (128, 255, 0, 255) ] * 4 - - elif self.shape == TILE_SHAPE_RAMP_NORTHEAST: - vertices = [ - (self.posX, self.posY, self.posZ + TILE_DEPTH), - (self.posX + TILE_WIDTH, self.posY, self.posZ + TILE_DEPTH), - (self.posX + TILE_WIDTH, self.posY + TILE_HEIGHT, self.posZ + TILE_DEPTH), - (self.posX, self.posY + TILE_HEIGHT, self.posZ) - ] - indices = [0, 1, 2, 0, 2, 3] - uvs = [ (0, 0), (1, 0), (1, 1), (0, 1) ] - colors = [ (0, 255, 128, 255) ] * 4 - - elif self.shape == TILE_SHAPE_RAMP_SOUTHEAST: - vertices = [ - (self.posX, self.posY, self.posZ), - (self.posX + TILE_WIDTH, self.posY, self.posZ + TILE_DEPTH), - (self.posX + TILE_WIDTH, self.posY + TILE_HEIGHT, self.posZ + TILE_DEPTH), - (self.posX, self.posY + TILE_HEIGHT, self.posZ + TILE_DEPTH) - ] - indices = [0, 1, 2, 0, 2, 3] - uvs = [ (0, 0), (1, 0), (1, 1), (0, 1) ] - colors = [ (255, 128, 255, 255) ] * 4 - - else: - # Solid black cube for unknown shape - x0, y0, z0 = self.posX, self.posY, self.posZ - x1, y1, z1 = self.posX + TILE_WIDTH, self.posY + TILE_HEIGHT, self.posZ + TILE_DEPTH - vertices = [ - (x0, y0, z0), (x1, y0, z0), (x1, y1, z0), (x0, y1, z0), # bottom - (x0, y0, z1), (x1, y0, z1), (x1, y1, z1), (x0, y1, z1) # top - ] - indices = [ - 0,1,2, 0,2,3, # bottom - 4,5,6, 4,6,7, # top - 0,1,5, 0,5,4, # front - 2,3,7, 2,7,6, # back - 1,2,6, 1,6,5, # right - 3,0,4, 3,4,7 # left - ] - uvs = [ (0,0) ] * 8 - colors = [ (0,0,0,255) ] * 8 - - return { - 'vertices': vertices, - 'indices': indices, - 'uvs': uvs, - 'colors': colors - } - - def buffer(self, vertexBuffer): - if self.shape == TILE_SHAPE_NULL: - return - - # New code: - baseData = self.getBaseTileModel() - - # Base data is indiced but we need to buffer unindiced data - for index in baseData['indices']: - verts = baseData['vertices'][index] - uv = baseData['uvs'][index] - color = baseData['colors'][index] - - vertexBuffer.vertices.extend([ - verts[0] - (TILE_WIDTH / 2.0), - verts[1] - (TILE_HEIGHT / 2.0), - verts[2] - (TILE_DEPTH / 2.0) - ]) - - vertexBuffer.colors.extend([ - color[0] / 255.0, - color[1] / 255.0, - color[2] / 255.0, - color[3] / 255.0 - ]) \ No newline at end of file diff --git a/archive/editor/__main__.py b/archive/editor/__main__.py deleted file mode 100755 index 90974893..00000000 --- a/archive/editor/__main__.py +++ /dev/null @@ -1,57 +0,0 @@ -#!/usr/bin/env python3 -import sys -from PyQt5.QtWidgets import ( - QApplication, QVBoxLayout, QPushButton, - QDialog -) -from OpenGL.GL import * -from OpenGL.GLU import * -from tools.editor.maptool import MapWindow -from tools.editor.langtool import LangToolWindow -from tools.editor.cutscenetool import CutsceneToolWindow - -DEFAULT_TOOL = None -DEFAULT_TOOL = "map" -# DEFAULT_TOOL = "cutscene" - -TOOLS = [ - ("Map Editor", "map", MapWindow), - ("Language Editor", "language", LangToolWindow), - ("Cutscene Editor", "cutscene", CutsceneToolWindow), -] - -class EditorChoiceDialog(QDialog): - def __init__(self): - super().__init__() - self.setWindowTitle("Choose Tool") - layout = QVBoxLayout(self) - self.selected = None - for label, key, win_cls in TOOLS: - btn = QPushButton(label) - btn.clicked.connect(lambda checked, w=win_cls: self.choose_tool(w)) - layout.addWidget(btn) - - def choose_tool(self, win_cls): - self.selected = win_cls - self.accept() - - def get_choice(self): - return self.selected - -def main(): - app = QApplication(sys.argv) - tool_map = { key: win_cls for _, key, win_cls in TOOLS } - if DEFAULT_TOOL in tool_map: - win_cls = tool_map[DEFAULT_TOOL] - else: - choice_dialog = EditorChoiceDialog() - if choice_dialog.exec_() == QDialog.Accepted: - win_cls = choice_dialog.get_choice() - else: - sys.exit(0) - win = win_cls() - win.show() - sys.exit(app.exec_()) - -if __name__ == "__main__": - main() diff --git a/archive/editor/cutscene/cutsceneitemeditor.py b/archive/editor/cutscene/cutsceneitemeditor.py deleted file mode 100644 index 0241ccdc..00000000 --- a/archive/editor/cutscene/cutsceneitemeditor.py +++ /dev/null @@ -1,58 +0,0 @@ -from PyQt5.QtWidgets import QWidget, QVBoxLayout, QLineEdit, QLabel, QSizePolicy, QComboBox, QHBoxLayout, QSpacerItem -from PyQt5.QtCore import Qt, pyqtSignal -from .cutscenewait import CutsceneWaitEditor -from .cutscenetext import CutsceneTextEditor - -EDITOR_MAP = ( - ( "wait", "Wait", CutsceneWaitEditor ), - ( "text", "Text", CutsceneTextEditor ) -) - -class CutsceneItemEditor(QWidget): - def __init__(self, parent=None): - super().__init__(parent) - self.layout = QVBoxLayout(self) - self.layout.setAlignment(Qt.AlignTop | Qt.AlignLeft) - self.layout.addWidget(QLabel("Item Properties:")) - - rowLayout = QHBoxLayout() - rowLayout.setSpacing(8) - - rowLayout.addWidget(QLabel("Name:")) - self.nameInput = QLineEdit() - rowLayout.addWidget(self.nameInput) - - rowLayout.addWidget(QLabel("Type:")) - self.typeDropdown = QComboBox() - self.typeDropdown.addItems([typeName for typeKey, typeName, editorClass in EDITOR_MAP]) - rowLayout.addWidget(self.typeDropdown) - self.layout.addLayout(rowLayout) - - self.activeEditor = None - - # Events - self.nameInput.textChanged.connect(self.onNameChanged) - self.typeDropdown.currentTextChanged.connect(self.onTypeChanged) - - # First load - self.onNameChanged(self.nameInput.text()) - self.onTypeChanged(self.typeDropdown.currentText()) - - def onNameChanged(self, nameText): - pass - - def onTypeChanged(self, typeText): - typeKey = typeText.lower() - - # Remove existing editor - if self.activeEditor: - self.layout.removeWidget(self.activeEditor) - self.activeEditor.deleteLater() - self.activeEditor = None - - # Create new editor - for key, name, editorClass in EDITOR_MAP: - if key == typeKey: - self.activeEditor = editorClass() - self.layout.addWidget(self.activeEditor) - break \ No newline at end of file diff --git a/archive/editor/cutscene/cutscenemenubar.py b/archive/editor/cutscene/cutscenemenubar.py deleted file mode 100644 index 991ad017..00000000 --- a/archive/editor/cutscene/cutscenemenubar.py +++ /dev/null @@ -1,54 +0,0 @@ -from PyQt5.QtWidgets import QMenuBar, QAction, QFileDialog, QMessageBox -from PyQt5.QtGui import QKeySequence - -class CutsceneMenuBar(QMenuBar): - def __init__(self, parent): - super().__init__(parent) - self.parent = parent - fileMenu = self.addMenu("File") - - self.newAction = QAction("New", self) - self.newAction.setShortcut(QKeySequence.New) - self.newAction.triggered.connect(self.newFile) - fileMenu.addAction(self.newAction) - - self.openAction = QAction("Open", self) - self.openAction.setShortcut(QKeySequence.Open) - self.openAction.triggered.connect(self.openFile) - fileMenu.addAction(self.openAction) - - self.saveAction = QAction("Save", self) - self.saveAction.setShortcut(QKeySequence.Save) - self.saveAction.triggered.connect(self.saveFile) - fileMenu.addAction(self.saveAction) - - self.saveAsAction = QAction("Save As", self) - self.saveAsAction.setShortcut(QKeySequence.SaveAs) - self.saveAsAction.triggered.connect(self.saveFileAs) - fileMenu.addAction(self.saveAsAction) - - def newFile(self): - self.parent.clearCutscene() - - def openFile(self): - path, _ = QFileDialog.getOpenFileName(self.parent, "Open Cutscene File", "", "JSON Files (*.json);;All Files (*)") - if not path: - return - - # TODO: Load file contents into timeline - self.parent.currentFile = path - pass - - def saveFile(self): - if not self.parent.currentFile: - self.saveFileAs() - return - - # TODO: Save timeline to self.parent.currentFile - pass - - def saveFileAs(self): - path, _ = QFileDialog.getSaveFileName(self.parent, "Save Cutscene File As", "", "JSON Files (*.json);;All Files (*)") - if path: - self.parent.currentFile = path - self.saveFile() diff --git a/archive/editor/cutscene/cutscenetext.py b/archive/editor/cutscene/cutscenetext.py deleted file mode 100644 index bf963dde..00000000 --- a/archive/editor/cutscene/cutscenetext.py +++ /dev/null @@ -1,21 +0,0 @@ -from PyQt5.QtWidgets import QWidget, QVBoxLayout, QLabel, QTextEdit -from PyQt5.QtCore import Qt - -class CutsceneTextEditor(QWidget): - def __init__(self, parent=None): - super().__init__(parent) - layout = QVBoxLayout(self) - layout.setContentsMargins(0, 0, 0, 0) - layout.setSpacing(0) - label = QLabel("Text:") - label.setSizePolicy(label.sizePolicy().Expanding, label.sizePolicy().Fixed) - layout.addWidget(label) - self.textInput = QTextEdit() - self.textInput.setSizePolicy(self.textInput.sizePolicy().Expanding, self.textInput.sizePolicy().Expanding) - layout.addWidget(self.textInput, stretch=1) - - def setText(self, text): - self.textInput.setPlainText(text) - - def getText(self): - return self.textInput.toPlainText() diff --git a/archive/editor/cutscene/cutscenewait.py b/archive/editor/cutscene/cutscenewait.py deleted file mode 100644 index 3dffb9cc..00000000 --- a/archive/editor/cutscene/cutscenewait.py +++ /dev/null @@ -1,20 +0,0 @@ -from PyQt5.QtWidgets import QWidget, QFormLayout, QDoubleSpinBox, QLabel - -class CutsceneWaitEditor(QWidget): - def __init__(self, parent=None): - super().__init__(parent) - layout = QFormLayout(self) - layout.setContentsMargins(0, 0, 0, 0) - layout.setSpacing(0) - self.waitTimeInput = QDoubleSpinBox() - self.waitTimeInput.setMinimum(0.0) - self.waitTimeInput.setMaximum(9999.0) - self.waitTimeInput.setDecimals(2) - self.waitTimeInput.setSingleStep(0.1) - layout.addRow(QLabel("Wait Time (seconds):"), self.waitTimeInput) - - def setWaitTime(self, value): - self.waitTimeInput.setValue(value) - - def getWaitTime(self): - return self.waitTimeInput.value() diff --git a/archive/editor/cutscenetool.py b/archive/editor/cutscenetool.py deleted file mode 100644 index 3612a6ac..00000000 --- a/archive/editor/cutscenetool.py +++ /dev/null @@ -1,101 +0,0 @@ -from PyQt5.QtWidgets import QMainWindow, QApplication, QWidget, QVBoxLayout, QHBoxLayout, QLabel, QPushButton, QListWidget, QListWidgetItem, QMenuBar, QAction, QFileDialog, QMessageBox -from PyQt5.QtGui import QKeySequence -from tools.editor.cutscene.cutsceneitemeditor import CutsceneItemEditor -from tools.editor.cutscene.cutscenemenubar import CutsceneMenuBar -import sys - -class CutsceneToolWindow(QMainWindow): - def __init__(self): - super().__init__() - self.setWindowTitle("Dusk Cutscene Editor") - self.setGeometry(100, 100, 800, 600) - self.nextItemNumber = 1 # Track next available number - self.currentFile = None - self.dirty = False # Track unsaved changes - - # File menu (handled by CutsceneMenuBar) - menubar = CutsceneMenuBar(self) - self.setMenuBar(menubar) - - # Main layout: horizontal split - central = QWidget() - mainLayout = QHBoxLayout(central) - self.setCentralWidget(central) - - # Timeline - leftPanel = QWidget() - leftLayout = QVBoxLayout(leftPanel) - - self.timelineList = QListWidget() - self.timelineList.setSelectionMode(QListWidget.SingleSelection) - leftLayout.addWidget(QLabel("Cutscene Timeline")) - leftLayout.addWidget(self.timelineList) - - btnLayout = QHBoxLayout() - self.addBtn = QPushButton("Add") - self.removeBtn = QPushButton("Remove") - btnLayout.addWidget(self.addBtn) - btnLayout.addWidget(self.removeBtn) - leftLayout.addLayout(btnLayout) - mainLayout.addWidget(leftPanel, 2) - - # Property editor - self.editorPanel = QWidget() - self.editorLayout = QVBoxLayout(self.editorPanel) - self.itemEditor = None # Only create when needed - mainLayout.addWidget(self.editorPanel, 3) - - # Events - self.timelineList.currentItemChanged.connect(self.onItemSelected) - self.addBtn.clicked.connect(self.addCutsceneItem) - self.removeBtn.clicked.connect(self.removeCutsceneItem) - - def addCutsceneItem(self): - name = f"Cutscene item {self.nextItemNumber}" - timelineItem = QListWidgetItem(name) - self.timelineList.addItem(timelineItem) - self.timelineList.setCurrentItem(timelineItem) - self.nextItemNumber += 1 - self.dirty = True - - def removeCutsceneItem(self): - row = self.timelineList.currentRow() - if row < 0: - return - self.timelineList.takeItem(row) - self.dirty = True - - # Remove editor if nothing selected - if self.timelineList.currentItem() is None or self.itemEditor is None: - return - - self.editorLayout.removeWidget(self.itemEditor) - self.itemEditor.deleteLater() - self.itemEditor = None - - def clearCutscene(self): - self.timelineList.clear() - self.nextItemNumber = 1 - self.currentFile = None - self.dirty = False - if self.itemEditor: - self.editorLayout.removeWidget(self.itemEditor) - - def onItemSelected(self, current, previous): - if current: - if not self.itemEditor: - self.itemEditor = CutsceneItemEditor() - self.editorLayout.addWidget(self.itemEditor) - return - - if not self.itemEditor: - return - self.editorLayout.removeWidget(self.itemEditor) - self.itemEditor.deleteLater() - self.itemEditor = None - -if __name__ == "__main__": - app = QApplication(sys.argv) - window = CutsceneToolWindow() - window.show() - sys.exit(app.exec_()) diff --git a/archive/editor/langtool.py b/archive/editor/langtool.py deleted file mode 100644 index ea6e108d..00000000 --- a/archive/editor/langtool.py +++ /dev/null @@ -1,202 +0,0 @@ -#!/usr/bin/env python3 -from PyQt5.QtWidgets import QMainWindow, QApplication, QAction, QMenuBar, QMessageBox, QFileDialog, QWidget, QVBoxLayout, QHBoxLayout, QLabel, QLineEdit, QTableWidget, QTableWidgetItem, QHeaderView, QPushButton, QTabWidget, QFormLayout -from PyQt5.QtCore import Qt -import sys -import os -import polib - -class LangToolWindow(QMainWindow): - def __init__(self): - super().__init__() - self.setWindowTitle("Dusk Language Editor") - self.setGeometry(100, 100, 800, 600) - self.current_file = None - self.dirty = False - self.init_menu() - self.init_ui() - - def init_ui(self): - central = QWidget() - layout = QVBoxLayout(central) - - tabs = QTabWidget() - # Header Tab - header_tab = QWidget() - header_layout = QFormLayout(header_tab) - self.language_edit = QLineEdit() - self.language_edit.setMaximumWidth(220) - header_layout.addRow(QLabel("Language:"), self.language_edit) - self.plural_edit = QLineEdit() - self.plural_edit.setMaximumWidth(320) - header_layout.addRow(QLabel("Plural-Forms:"), self.plural_edit) - self.content_type_edit = QLineEdit("text/plain; charset=UTF-8") - self.content_type_edit.setMaximumWidth(320) - header_layout.addRow(QLabel("Content-Type:"), self.content_type_edit) - tabs.addTab(header_tab, "Header") - - # Strings Tab - strings_tab = QWidget() - strings_layout = QVBoxLayout(strings_tab) - self.po_table = QTableWidget() - self.po_table.setColumnCount(2) - self.po_table.setHorizontalHeaderLabels(["msgid", "msgstr"]) - self.po_table.horizontalHeader().setSectionResizeMode(QHeaderView.Stretch) - self.po_table.verticalHeader().setMinimumWidth(22) - self.po_table.verticalHeader().setDefaultAlignment(Qt.AlignRight | Qt.AlignVCenter) - strings_layout.addWidget(self.po_table) - row_btn_layout = QHBoxLayout() - add_row_btn = QPushButton("Add Row") - remove_row_btn = QPushButton("Remove Row") - row_btn_layout.addWidget(add_row_btn) - row_btn_layout.addWidget(remove_row_btn) - strings_layout.addLayout(row_btn_layout) - tabs.addTab(strings_tab, "Strings") - - layout.addWidget(tabs) - - add_row_btn.clicked.connect(self.add_row) - remove_row_btn.clicked.connect(self.remove_row) - self.add_row_btn = add_row_btn - self.remove_row_btn = remove_row_btn - - self.setCentralWidget(central) - - # Connect edits to dirty flag - self.language_edit.textChanged.connect(self.set_dirty) - self.plural_edit.textChanged.connect(self.set_dirty) - self.content_type_edit.textChanged.connect(self.set_dirty) - self.po_table.itemChanged.connect(self.set_dirty) - - def set_dirty(self): - self.dirty = True - self.update_save_action() - - def init_menu(self): - menubar = self.menuBar() - file_menu = menubar.addMenu("File") - - new_action = QAction("New", self) - open_action = QAction("Open", self) - save_action = QAction("Save", self) - save_as_action = QAction("Save As", self) - - new_action.triggered.connect(lambda: self.handle_file_action("new")) - open_action.triggered.connect(lambda: self.handle_file_action("open")) - save_action.triggered.connect(self.handle_save) - save_as_action.triggered.connect(self.handle_save_as) - - file_menu.addAction(new_action) - file_menu.addAction(open_action) - file_menu.addAction(save_action) - file_menu.addAction(save_as_action) - - self.save_action = save_action # Store reference for enabling/disabling - self.update_save_action() - - def update_save_action(self): - self.save_action.setEnabled(self.current_file is not None) - - def handle_file_action(self, action): - if self.dirty: - reply = QMessageBox.question(self, "Save Changes?", "Do you want to save changes before proceeding?", QMessageBox.Yes | QMessageBox.No | QMessageBox.Cancel) - if reply == QMessageBox.Cancel: - return - elif reply == QMessageBox.Yes: - self.handle_save() - if action == "new": - self.new_file() - elif action == "open": - default_dir = os.path.abspath(os.path.join(os.path.dirname(__file__), "../../assets/locale")) - file_path, _ = QFileDialog.getOpenFileName(self, "Open Language File", default_dir, "PO Files (*.po)") - if file_path: - self.open_file(file_path) - - def new_file(self): - self.current_file = None - self.dirty = False - self.language_edit.setText("") - self.plural_edit.setText("") - self.po_table.setRowCount(0) - self.update_save_action() - - def open_file(self, file_path): - self.current_file = file_path - self.dirty = False - self.update_save_action() - self.load_po_file(file_path) - - def load_po_file(self, file_path): - po = polib.pofile(file_path) - language = po.metadata.get('Language', '') - plural = po.metadata.get('Plural-Forms', '') - content_type = po.metadata.get('Content-Type', 'text/plain; charset=UTF-8') - self.language_edit.setText(language) - self.plural_edit.setText(plural) - self.content_type_edit.setText(content_type) - self.po_table.setRowCount(len(po)) - for row, entry in enumerate(po): - self.po_table.setItem(row, 0, QTableWidgetItem(entry.msgid)) - self.po_table.setItem(row, 1, QTableWidgetItem(entry.msgstr)) - - def save_file(self, file_path): - po = polib.POFile() - po.metadata = { - 'Language': self.language_edit.text(), - 'Content-Type': self.content_type_edit.text(), - 'Plural-Forms': self.plural_edit.text(), - } - for row in range(self.po_table.rowCount()): - msgid_item = self.po_table.item(row, 0) - msgstr_item = self.po_table.item(row, 1) - msgid = msgid_item.text() if msgid_item else '' - msgstr = msgstr_item.text() if msgstr_item else '' - if msgid or msgstr: - entry = polib.POEntry(msgid=msgid, msgstr=msgstr) - po.append(entry) - po.save(file_path) - self.dirty = False - self.update_save_action() - - def handle_save(self): - if self.current_file: - self.save_file(self.current_file) - else: - self.handle_save_as() - - def handle_save_as(self): - default_dir = os.path.abspath(os.path.join(os.path.dirname(__file__), "../../assets/locale")) - file_path, _ = QFileDialog.getSaveFileName(self, "Save Language File As", default_dir, "PO Files (*.po)") - if file_path: - self.current_file = file_path - self.update_save_action() - self.save_file(file_path) - - def add_row(self): - row = self.po_table.rowCount() - self.po_table.insertRow(row) - self.po_table.setItem(row, 0, QTableWidgetItem("")) - self.po_table.setItem(row, 1, QTableWidgetItem("")) - self.po_table.setCurrentCell(row, 0) - self.set_dirty() - - def remove_row(self): - row = self.po_table.currentRow() - if row >= 0: - self.po_table.removeRow(row) - self.set_dirty() - - def closeEvent(self, event): - if self.dirty: - reply = QMessageBox.question(self, "Save Changes?", "Do you want to save changes before exiting?", QMessageBox.Yes | QMessageBox.No | QMessageBox.Cancel) - if reply == QMessageBox.Cancel: - event.ignore() - return - elif reply == QMessageBox.Yes: - self.handle_save() - event.accept() - -if __name__ == "__main__": - app = QApplication(sys.argv) - window = LangToolWindow() - window.show() - sys.exit(app.exec_()) diff --git a/archive/editor/map/camera.py b/archive/editor/map/camera.py deleted file mode 100644 index e7854fa6..00000000 --- a/archive/editor/map/camera.py +++ /dev/null @@ -1,55 +0,0 @@ -import math -import time -from OpenGL.GL import * -from OpenGL.GLU import * -from tools.dusk.defs import TILE_WIDTH, TILE_HEIGHT, TILE_DEPTH, RPG_CAMERA_PIXELS_PER_UNIT, RPG_CAMERA_Z_OFFSET, RPG_CAMERA_FOV - -class Camera: - def __init__(self, parent): - self.parent = parent - self.pixelsPerUnit = RPG_CAMERA_PIXELS_PER_UNIT - self.yOffset = RPG_CAMERA_Z_OFFSET - self.fov = RPG_CAMERA_FOV - self.scale = 8.0 - self.lastTime = time.time() - self.lookAtTarget = [0.0, 0.0, 0.0] - - def setup(self, vw, vh, duration=0.1): - now = time.time() - delta = now - self.lastTime - self.lastTime = now - # Calculate ease factor for exponential smoothing over 'duration' seconds - ease = 1 - math.exp(-delta / duration) - - z = (vh / 2.0) / ( - (self.pixelsPerUnit * self.scale) * math.tan(math.radians(self.fov / 2.0)) - ) - lookAt = [ - self.parent.map.position[0] * TILE_WIDTH, - self.parent.map.position[1] * TILE_HEIGHT, - self.parent.map.position[2] * TILE_DEPTH, - ] - aspectRatio = vw / vh - - # Ease the lookAt target - for i in range(3): - self.lookAtTarget[i] += (lookAt[i] - self.lookAtTarget[i]) * ease - - # Camera position is now based on the eased lookAtTarget - cameraPosition = ( - self.lookAtTarget[0], - self.lookAtTarget[1] + self.yOffset, - self.lookAtTarget[2] + z - ) - - glMatrixMode(GL_PROJECTION) - glLoadIdentity() - gluPerspective(self.fov, aspectRatio, 0.1, 1000.0) - glScalef(1.0, -1.0, 1.0) # Flip the projection matrix upside down - glMatrixMode(GL_MODELVIEW) - glLoadIdentity() - gluLookAt( - cameraPosition[0], cameraPosition[1], cameraPosition[2], - self.lookAtTarget[0], self.lookAtTarget[1], self.lookAtTarget[2], - 0.0, 1.0, 0.0 - ) \ No newline at end of file diff --git a/archive/editor/map/chunkpanel.py b/archive/editor/map/chunkpanel.py deleted file mode 100644 index 0ad87e47..00000000 --- a/archive/editor/map/chunkpanel.py +++ /dev/null @@ -1,80 +0,0 @@ -from PyQt5.QtWidgets import QWidget, QVBoxLayout, QLabel, QGridLayout, QTreeWidget, QTreeWidgetItem, QComboBox -from tools.dusk.defs import CHUNK_WIDTH, CHUNK_HEIGHT, CHUNK_DEPTH, TILE_SHAPES - -class ChunkPanel(QWidget): - def __init__(self, parent): - super().__init__(parent) - self.parent = parent - layout = QVBoxLayout(self) - - # Tile shape dropdown - self.tileShapeDropdown = QComboBox() - self.tileShapeDropdown.addItems(TILE_SHAPES.keys()) - self.tileShapeDropdown.setToolTip("Tile Shape") - layout.addWidget(self.tileShapeDropdown) - - # Add expandable tree list - self.tree = QTreeWidget() - self.tree.setHeaderLabel("Chunks") - self.tree.expandAll() # Expand by default, remove if you want collapsed - layout.addWidget(self.tree) # Removed invalid stretch factor - - # Add stretch so tree expands - layout.setStretchFactor(self.tree, 1) - - # Event subscriptions - self.parent.map.onMapData.sub(self.onMapData) - self.parent.map.onPositionChange.sub(self.onPositionChange) - self.tileShapeDropdown.currentTextChanged.connect(self.onTileShapeChanged) - - # For each chunk - for chunk in self.parent.map.chunks.values(): - # Create tree element - item = QTreeWidgetItem(self.tree, ["Chunk ({}, {}, {})".format(chunk.x, chunk.y, chunk.z)]) - chunk.chunkPanelTree = item - chunk.chunkPanelTree.setExpanded(True) - item.setData(0, 0, chunk) # Store chunk reference - - chunk.onChunkData.sub(self.onChunkData) - - def onMapData(self, data): - pass - - def onPositionChange(self, pos): - self.updateChunkList() - - tile = self.parent.map.getTileAtWorldPos(*self.parent.map.position) - if tile is None: - return - - key = "TILE_SHAPE_NULL" - for k, v in TILE_SHAPES.items(): - if v != tile.shape: - continue - key = k - break - self.tileShapeDropdown.setCurrentText(key) - - def onTileShapeChanged(self, shape_key): - tile = self.parent.map.getTileAtWorldPos(*self.parent.map.position) - - if tile is None or shape_key not in TILE_SHAPES: - return - tile.setShape(TILE_SHAPES[shape_key]) - - def updateChunkList(self): - # Clear existing items - currentChunk = self.parent.map.getChunkAtWorldPos(*self.parent.map.position) - - # Example tree items - for chunk in self.parent.map.chunks.values(): - title = "Chunk ({}, {}, {})".format(chunk.x, chunk.y, chunk.z) - if chunk == currentChunk: - title += " [C]" - if chunk.isDirty(): - title += " *" - item = chunk.chunkPanelTree - item.setText(0, title) - - def onChunkData(self, chunk): - self.updateChunkList() \ No newline at end of file diff --git a/archive/editor/map/entitypanel.py b/archive/editor/map/entitypanel.py deleted file mode 100644 index 5ca2fd86..00000000 --- a/archive/editor/map/entitypanel.py +++ /dev/null @@ -1,154 +0,0 @@ -from PyQt5.QtWidgets import QWidget, QVBoxLayout, QLabel, QComboBox, QHBoxLayout, QPushButton, QLineEdit, QListWidget, QListWidgetItem -from PyQt5.QtCore import Qt -from tools.dusk.entity import Entity -from tools.dusk.defs import CHUNK_WIDTH, CHUNK_HEIGHT, CHUNK_DEPTH, ENTITY_TYPES, ENTITY_TYPE_NULL - -class EntityPanel(QWidget): - def __init__(self, parent): - super().__init__(parent) - self.parent = parent - layout = QVBoxLayout(self) - self.setLayout(layout) - - # Top panel placeholder - topWidget = QLabel("Entity Editor") - layout.addWidget(topWidget) - - # Name input - nameLayout = QHBoxLayout() - nameLabel = QLabel("Name:") - self.nameInput = QLineEdit() - nameLayout.addWidget(nameLabel) - nameLayout.addWidget(self.nameInput) - layout.addLayout(nameLayout) - - # Entity Type dropdown (single selection) - typeLayout = QHBoxLayout() - typeLabel = QLabel("Type:") - self.typeDropdown = QComboBox() - self.typeDropdown.addItems(ENTITY_TYPES) - typeLayout.addWidget(typeLabel) - typeLayout.addWidget(self.typeDropdown) - layout.addLayout(typeLayout) - - # Entity list and buttons - self.entityList = QListWidget() - self.entityList.addItems([]) - layout.addWidget(self.entityList, stretch=1) - - btnLayout = QHBoxLayout() - self.btnAdd = QPushButton("Add") - self.btnRemove = QPushButton("Remove") - btnLayout.addWidget(self.btnAdd) - btnLayout.addWidget(self.btnRemove) - layout.addLayout(btnLayout) - - # Events - self.btnAdd.clicked.connect(self.onAddEntity) - self.btnRemove.clicked.connect(self.onRemoveEntity) - self.parent.map.onEntityData.sub(self.onEntityData) - self.parent.map.onPositionChange.sub(self.onPositionChange) - self.entityList.itemClicked.connect(self.onEntityClicked) - self.entityList.itemDoubleClicked.connect(self.onEntityDoubleClicked) - self.typeDropdown.currentIndexChanged.connect(self.onTypeSelected) - self.nameInput.textChanged.connect(self.onNameChanged) - - # Call once to populate - self.onEntityData() - self.onEntityUnselect() - - def onEntityUnselect(self): - self.entityList.setCurrentItem(None) - self.nameInput.setText("") - self.typeDropdown.setCurrentIndex(ENTITY_TYPE_NULL) - - def onEntitySelect(self, entity): - self.entityList.setCurrentItem(entity.item) - self.nameInput.setText(entity.name) - self.typeDropdown.setCurrentIndex(entity.type) - - def onEntityDoubleClicked(self, item): - entity = item.data(Qt.UserRole) - chunk = entity.chunk - worldX = (chunk.x * CHUNK_WIDTH) + entity.localX - worldY = (chunk.y * CHUNK_HEIGHT) + entity.localY - worldZ = (chunk.z * CHUNK_DEPTH) + entity.localZ - self.parent.map.moveTo(worldX, worldY, worldZ) - - def onEntityClicked(self, item): - pass - - def onAddEntity(self): - chunk = self.parent.map.getChunkAtWorldPos(*self.parent.map.position) - if chunk is None: - return - - localX = (self.parent.map.position[0] - (chunk.x * CHUNK_WIDTH)) % CHUNK_WIDTH - localY = (self.parent.map.position[1] - (chunk.y * CHUNK_HEIGHT)) % CHUNK_HEIGHT - localZ = (self.parent.map.position[2] - (chunk.z * CHUNK_DEPTH)) % CHUNK_DEPTH - - # Make sure there's not already an entity here - for ent in chunk.entities.values(): - if ent.localX == localX and ent.localY == localY and ent.localZ == localZ: - print("Entity already exists at this location") - return - - ent = chunk.addEntity(localX, localY, localZ) - - def onRemoveEntity(self): - item = self.entityList.currentItem() - if item is None: - return - entity = item.data(Qt.UserRole) - if entity: - chunk = entity.chunk - chunk.removeEntity(entity) - pass - - def onEntityData(self): - self.onEntityUnselect() - self.entityList.clear() - for chunk in self.parent.map.chunks.values(): - for id, entity in chunk.entities.items(): - item = QListWidgetItem(entity.name) - item.setData(Qt.UserRole, entity) # Store the entity object - entity.item = item - self.entityList.addItem(item) - - # Select if there is something at current position - self.onPositionChange(self.parent.map.position) - - def onPositionChange(self, position): - self.onEntityUnselect() - - # Get Entity at.. - chunk = self.parent.map.getChunkAtWorldPos(*position) - if chunk is None: - return - - localX = (position[0] - (chunk.x * CHUNK_WIDTH)) % CHUNK_WIDTH - localY = (position[1] - (chunk.y * CHUNK_HEIGHT)) % CHUNK_HEIGHT - localZ = (position[2] - (chunk.z * CHUNK_DEPTH)) % CHUNK_DEPTH - - for ent in chunk.entities.values(): - if ent.localX != localX or ent.localY != localY or ent.localZ != localZ: - continue - self.onEntitySelect(ent) - self.entityList.setCurrentItem(ent.item) - break - - def onTypeSelected(self, index): - item = self.entityList.currentItem() - if item is None: - return - entity = item.data(Qt.UserRole) - if entity: - entity.setType(index) - - def onNameChanged(self, text): - item = self.entityList.currentItem() - if item is None: - return - entity = item.data(Qt.UserRole) - if entity: - entity.setName(text) \ No newline at end of file diff --git a/archive/editor/map/glwidget.py b/archive/editor/map/glwidget.py deleted file mode 100644 index 3f18d318..00000000 --- a/archive/editor/map/glwidget.py +++ /dev/null @@ -1,41 +0,0 @@ -from PyQt5.QtCore import QTimer -from PyQt5.QtWidgets import QOpenGLWidget -from OpenGL.GL import * -from OpenGL.GLU import * - -class GLWidget(QOpenGLWidget): - def __init__(self, parent): - super().__init__(parent) - self.parent = parent - self.timer = QTimer(self) - self.timer.timeout.connect(self.update) - self.timer.start(16) # ~60 FPS - - def initializeGL(self): - glClearColor(0.392, 0.584, 0.929, 1.0) - glEnable(GL_DEPTH_TEST) - glEnable(GL_BLEND) - glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA) - glEnable(GL_POLYGON_OFFSET_FILL) - glPolygonOffset(1.0, 1.0) - glDisable(GL_POLYGON_OFFSET_FILL) - - def resizeGL(self, w, h): - pass - - def paintGL(self): - glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT) - glLoadIdentity() - - w = self.width() - h = self.height() - if h <= 0: - h = 1 - if w <= 0: - w = 1 - - glViewport(0, 0, w, h) - self.parent.camera.setup(w, h) - self.parent.grid.draw() - self.parent.map.draw() - self.parent.selectBox.draw() \ No newline at end of file diff --git a/archive/editor/map/grid.py b/archive/editor/map/grid.py deleted file mode 100644 index 5697c500..00000000 --- a/archive/editor/map/grid.py +++ /dev/null @@ -1,46 +0,0 @@ -from OpenGL.GL import * -from tools.dusk.defs import TILE_WIDTH, TILE_HEIGHT, TILE_DEPTH - -class Grid: - def __init__(self, lines=1000): - self.cellWidth = TILE_WIDTH - self.cellHeight = TILE_HEIGHT - self.lines = lines - self.enabled = True - - def draw(self): - if not self.enabled: - return - center = [0.01,0.01,0.01] - halfWidth = self.cellWidth * self.lines // 2 - halfHeight = self.cellHeight * self.lines // 2 - # Draw origin axes - glBegin(GL_LINES) - - # X axis - RED - glColor3f(1.0, 0.0, 0.0) - glVertex3f(center[0] - halfWidth, center[1], center[2]) - glVertex3f(center[0] + halfWidth, center[1], center[2]) - - # Y axis - GREEN - glColor3f(0.0, 1.0, 0.0) - glVertex3f(center[0], center[1] - halfHeight, center[2]) - glVertex3f(center[0], center[1] + halfHeight, center[2]) - - # Z axis - BLUE - glColor3f(0.0, 0.0, 1.0) - glVertex3f(center[0], center[1], center[2] - halfWidth) - glVertex3f(center[0], center[1], center[2] + halfWidth) - glEnd() - - # Draw grid - glColor3f(0.8, 0.8, 0.8) - glBegin(GL_LINES) - for i in range(-self.lines // 2, self.lines // 2 + 1): - # Vertical lines - glVertex3f(center[0] + i * self.cellWidth, center[1] - halfHeight, center[2]) - glVertex3f(center[0] + i * self.cellWidth, center[1] + halfHeight, center[2]) - # Horizontal lines - glVertex3f(center[0] - halfWidth, center[1] + i * self.cellHeight, center[2]) - glVertex3f(center[0] + halfWidth, center[1] + i * self.cellHeight, center[2]) - glEnd() \ No newline at end of file diff --git a/archive/editor/map/mapinfopanel.py b/archive/editor/map/mapinfopanel.py deleted file mode 100644 index 2d0f6127..00000000 --- a/archive/editor/map/mapinfopanel.py +++ /dev/null @@ -1,83 +0,0 @@ -from PyQt5.QtWidgets import QWidget, QVBoxLayout, QLabel, QLineEdit, QPushButton, QHBoxLayout, QMessageBox -from tools.dusk.defs import CHUNK_WIDTH, CHUNK_HEIGHT, CHUNK_DEPTH - -class MapInfoPanel(QWidget): - def __init__(self, parent): - super().__init__(parent) - self.parent = parent - - # Components - layout = QVBoxLayout() - - mapTitleLabel = QLabel("Map Title") - self.mapTitleInput = QLineEdit() - layout.addWidget(mapTitleLabel) - layout.addWidget(self.mapTitleInput) - - tilePositionLabel = QLabel("Tile Position") - layout.addWidget(tilePositionLabel) - tilePositionRow = QHBoxLayout() - self.tileXInput = QLineEdit() - self.tileXInput.setPlaceholderText("X") - tilePositionRow.addWidget(self.tileXInput) - self.tileYInput = QLineEdit() - self.tileYInput.setPlaceholderText("Y") - tilePositionRow.addWidget(self.tileYInput) - self.tileZInput = QLineEdit() - self.tileZInput.setPlaceholderText("Z") - tilePositionRow.addWidget(self.tileZInput) - self.tileGo = QPushButton("Go") - tilePositionRow.addWidget(self.tileGo) - layout.addLayout(tilePositionRow) - - self.chunkPosLabel = QLabel() - layout.addWidget(self.chunkPosLabel) - self.chunkLabel = QLabel() - layout.addWidget(self.chunkLabel) - - layout.addStretch() - self.setLayout(layout) - - # Events - self.tileGo.clicked.connect(self.goToPosition) - self.tileXInput.returnPressed.connect(self.goToPosition) - self.tileYInput.returnPressed.connect(self.goToPosition) - self.tileZInput.returnPressed.connect(self.goToPosition) - self.parent.map.onPositionChange.sub(self.updatePositionLabels) - self.parent.map.onMapData.sub(self.onMapData) - self.mapTitleInput.textChanged.connect(self.onMapNameChange) - - # Initial label setting - self.updatePositionLabels(self.parent.map.position) - - def goToPosition(self): - try: - x = int(self.tileXInput.text()) - y = int(self.tileYInput.text()) - z = int(self.tileZInput.text()) - self.parent.map.moveTo(x, y, z) - except ValueError: - QMessageBox.warning(self, "Invalid Input", "Please enter valid integer coordinates.") - - def updatePositionLabels(self, pos): - self.tileXInput.setText(str(pos[0])) - self.tileYInput.setText(str(pos[1])) - self.tileZInput.setText(str(pos[2])) - - chunkTileX = pos[0] % CHUNK_WIDTH - chunkTileY = pos[1] % CHUNK_HEIGHT - chunkTileZ = pos[2] % CHUNK_DEPTH - chunkTileIndex = chunkTileX + chunkTileY * CHUNK_WIDTH + chunkTileZ * CHUNK_WIDTH * CHUNK_HEIGHT - self.chunkPosLabel.setText(f"Chunk Position: {chunkTileX}, {chunkTileY}, {chunkTileZ} ({chunkTileIndex})") - - chunkX = pos[0] // CHUNK_WIDTH - chunkY = pos[1] // CHUNK_HEIGHT - chunkZ = pos[2] // CHUNK_DEPTH - self.chunkLabel.setText(f"Chunk: {chunkX}, {chunkY}, {chunkZ}") - - def onMapData(self, data): - self.updatePositionLabels(self.parent.map.position) - self.mapTitleInput.setText(data.get("mapName", "")) - - def onMapNameChange(self, text): - self.parent.map.data['mapName'] = text \ No newline at end of file diff --git a/archive/editor/map/mapleftpanel.py b/archive/editor/map/mapleftpanel.py deleted file mode 100644 index b25e5e6c..00000000 --- a/archive/editor/map/mapleftpanel.py +++ /dev/null @@ -1,51 +0,0 @@ -from PyQt5.QtWidgets import QWidget, QVBoxLayout, QGridLayout, QPushButton, QTabWidget, QLabel -from tools.editor.map.chunkpanel import ChunkPanel -from tools.editor.map.entitypanel import EntityPanel -from tools.editor.map.regionpanel import RegionPanel - -class MapLeftPanel(QWidget): - def __init__(self, parent): - super().__init__(parent) - self.parent = parent - layout = QVBoxLayout(self) - self.setLayout(layout) - - # Nav buttons - self.chunkInfoLabel = QLabel("Tile Information") - layout.addWidget(self.chunkInfoLabel) - grid = QGridLayout() - self.btnUp = QPushButton("U") - self.btnN = QPushButton("N") - self.btnDown = QPushButton("D") - self.btnW = QPushButton("W") - self.btnS = QPushButton("S") - self.btnE = QPushButton("E") - - grid.addWidget(self.btnUp, 0, 0) - grid.addWidget(self.btnN, 0, 1) - grid.addWidget(self.btnDown, 0, 2) - grid.addWidget(self.btnW, 1, 0) - grid.addWidget(self.btnS, 1, 1) - grid.addWidget(self.btnE, 1, 2) - layout.addLayout(grid) - - # Panels - self.chunkPanel = ChunkPanel(self.parent) - self.entityPanel = EntityPanel(self.parent) - self.regionPanel = RegionPanel(self.parent) - - # Tabs - self.tabs = QTabWidget() - self.tabs.addTab(self.chunkPanel, "Tiles") - self.tabs.addTab(self.entityPanel, "Entities") - self.tabs.addTab(self.regionPanel, "Regions") - self.tabs.addTab(None, "Triggers") - layout.addWidget(self.tabs) - - # Event subscriptions - self.btnN.clicked.connect(lambda: self.parent.map.moveRelative(0, -1, 0)) - self.btnS.clicked.connect(lambda: self.parent.map.moveRelative(0, 1, 0)) - self.btnE.clicked.connect(lambda: self.parent.map.moveRelative(1, 0, 0)) - self.btnW.clicked.connect(lambda: self.parent.map.moveRelative(-1, 0, 0)) - self.btnUp.clicked.connect(lambda: self.parent.map.moveRelative(0, 0, 1)) - self.btnDown.clicked.connect(lambda: self.parent.map.moveRelative(0, 0, -1)) diff --git a/archive/editor/map/menubar.py b/archive/editor/map/menubar.py deleted file mode 100644 index 5084ed1c..00000000 --- a/archive/editor/map/menubar.py +++ /dev/null @@ -1,46 +0,0 @@ -import os -from PyQt5.QtWidgets import QAction, QMenuBar, QFileDialog -from PyQt5.QtGui import QKeySequence -from tools.dusk.map import MAP_DEFAULT_PATH - -class MapMenubar: - def __init__(self, parent): - self.parent = parent - self.menubar = QMenuBar(parent) - parent.setMenuBar(self.menubar) - - self.fileMenu = self.menubar.addMenu("File") - self.actionNew = QAction("New", parent) - self.actionOpen = QAction("Open", parent) - self.actionSave = QAction("Save", parent) - self.actionSaveAs = QAction("Save As", parent) - - self.actionNew.setShortcut(QKeySequence("Ctrl+N")) - self.actionOpen.setShortcut(QKeySequence("Ctrl+O")) - self.actionSave.setShortcut(QKeySequence("Ctrl+S")) - self.actionSaveAs.setShortcut(QKeySequence("Ctrl+Shift+S")) - - self.actionNew.triggered.connect(self.newFile) - self.actionOpen.triggered.connect(self.openFile) - self.actionSave.triggered.connect(self.saveFile) - self.actionSaveAs.triggered.connect(self.saveAsFile) - self.fileMenu.addAction(self.actionNew) - self.fileMenu.addAction(self.actionOpen) - self.fileMenu.addAction(self.actionSave) - self.fileMenu.addAction(self.actionSaveAs) - - def newFile(self): - self.parent.map.newFile() - - def openFile(self): - filePath, _ = QFileDialog.getOpenFileName(self.menubar, "Open Map File", MAP_DEFAULT_PATH, "Map Files (*.json)") - if filePath: - self.parent.map.load(filePath) - - def saveFile(self): - self.parent.map.save() - - def saveAsFile(self): - filePath, _ = QFileDialog.getSaveFileName(self.menubar, "Save Map File As", MAP_DEFAULT_PATH, "Map Files (*.json)") - if filePath: - self.parent.map.save(filePath) \ No newline at end of file diff --git a/archive/editor/map/regionpanel.py b/archive/editor/map/regionpanel.py deleted file mode 100644 index c2f1ee49..00000000 --- a/archive/editor/map/regionpanel.py +++ /dev/null @@ -1,15 +0,0 @@ -from PyQt5.QtWidgets import QWidget, QVBoxLayout, QLabel, QComboBox, QHBoxLayout, QPushButton, QLineEdit, QListWidget, QListWidgetItem -from PyQt5.QtCore import Qt -from tools.dusk.entity import Entity -from tools.dusk.defs import CHUNK_WIDTH, CHUNK_HEIGHT, CHUNK_DEPTH, ENTITY_TYPES, ENTITY_TYPE_NULL - -class RegionPanel(QWidget): - def __init__(self, parent): - super().__init__(parent) - self.parent = parent - layout = QVBoxLayout(self) - self.setLayout(layout) - - # Top panel placeholder - topWidget = QLabel("Region Editor") - layout.addWidget(topWidget) \ No newline at end of file diff --git a/archive/editor/map/selectbox.py b/archive/editor/map/selectbox.py deleted file mode 100644 index 9af53ca9..00000000 --- a/archive/editor/map/selectbox.py +++ /dev/null @@ -1,44 +0,0 @@ -import OpenGL.GL as gl -from tools.dusk.defs import defs -import colorsys -from tools.dusk.defs import TILE_WIDTH, TILE_HEIGHT, TILE_DEPTH - -class SelectBox: - def __init__(self, parent): - self.parent = parent - self.hue = 0.0 - - def draw(self): - position = [ - self.parent.map.position[0] * TILE_WIDTH - (TILE_WIDTH / 2.0), - self.parent.map.position[1] * TILE_HEIGHT - (TILE_HEIGHT / 2.0), - self.parent.map.position[2] * TILE_DEPTH - (TILE_DEPTH / 2.0) - ] - - vertices = [ - (position[0], position[1], position[2]), - (position[0]+TILE_WIDTH, position[1], position[2]), - (position[0]+TILE_WIDTH, position[1]+TILE_HEIGHT, position[2]), - (position[0], position[1]+TILE_HEIGHT, position[2]), - (position[0], position[1], position[2]+TILE_DEPTH), - (position[0]+TILE_WIDTH, position[1], position[2]+TILE_DEPTH), - (position[0]+TILE_WIDTH, position[1]+TILE_HEIGHT, position[2]+TILE_DEPTH), - (position[0], position[1]+TILE_HEIGHT, position[2]+TILE_DEPTH) - ] - edges = [ - (0, 1), (1, 2), (2, 3), (3, 0), # bottom face - (4, 5), (5, 6), (6, 7), (7, 4), # top face - (4, 5), (5, 6), (6, 7), (7, 4), # top face - (0, 4), (1, 5), (2, 6), (3, 7) # vertical edges - ] - - # Cycle hue - self.hue = (self.hue + 0.01) % 1.0 - r, g, b = colorsys.hsv_to_rgb(self.hue, 1.0, 1.0) - gl.glColor3f(r, g, b) - gl.glLineWidth(2.0) - gl.glBegin(gl.GL_LINES) - for edge in edges: - for vertex in edge: - gl.glVertex3f(*vertices[vertex]) - gl.glEnd() \ No newline at end of file diff --git a/archive/editor/map/statusbar.py b/archive/editor/map/statusbar.py deleted file mode 100644 index ccb167ac..00000000 --- a/archive/editor/map/statusbar.py +++ /dev/null @@ -1,18 +0,0 @@ -from PyQt5.QtWidgets import QStatusBar, QLabel - -class StatusBar(QStatusBar): - def __init__(self, parent=None): - super().__init__(parent) - self.parent = parent - self.leftLabel = QLabel("") - self.rightLabel = QLabel("") - self.addWidget(self.leftLabel, 1) - self.addPermanentWidget(self.rightLabel) - - parent.map.onMapData.sub(self.onMapData) - - def setStatus(self, message): - self.leftLabel.setText(message) - - def onMapData(self, data): - self.rightLabel.setText(self.parent.map.mapFileName if self.parent.map.mapFileName else "Untitled.json") \ No newline at end of file diff --git a/archive/editor/map/vertexbuffer.py b/archive/editor/map/vertexbuffer.py deleted file mode 100644 index fc804fe3..00000000 --- a/archive/editor/map/vertexbuffer.py +++ /dev/null @@ -1,80 +0,0 @@ -from OpenGL.GL import * -import array - -class VertexBuffer: - def __init__(self, componentsPerVertex=3): - self.componentsPerVertex = componentsPerVertex - self.vertices = [] - self.colors = [] - self.uvs = [] - self.data = None - self.colorData = None - self.uvData = None - - def buildData(self): - hasColors = len(self.colors) > 0 - hasUvs = len(self.uvs) > 0 - - vertexCount = len(self.vertices) // self.componentsPerVertex - - dataList = [] - colorList = [] - uvList = [] - - for i in range(vertexCount): - vStart = i * self.componentsPerVertex - dataList.extend(self.vertices[vStart:vStart+self.componentsPerVertex]) - - if hasColors: - cStart = i * 4 # Assuming RGBA - colorList.extend(self.colors[cStart:cStart+4]) - - if hasUvs: - uStart = i * 2 # Assuming UV - uvList.extend(self.uvs[uStart:uStart+2]) - - self.data = array.array('f', dataList) - - if hasColors: - self.colorData = array.array('f', colorList) - else: - self.colorData = None - - if hasUvs: - self.uvData = array.array('f', uvList) - else: - self.uvData = None - - def draw(self, mode=GL_TRIANGLES, count=-1): - if count == -1: - count = len(self.data) // self.componentsPerVertex - - if count == 0: - return - - glEnableClientState(GL_VERTEX_ARRAY) - glVertexPointer(self.componentsPerVertex, GL_FLOAT, 0, self.data.tobytes()) - - if self.colorData: - glEnableClientState(GL_COLOR_ARRAY) - glColorPointer(4, GL_FLOAT, 0, self.colorData.tobytes()) - - if self.uvData: - glEnableClientState(GL_TEXTURE_COORD_ARRAY) - glTexCoordPointer(2, GL_FLOAT, 0, self.uvData.tobytes()) - - glDrawArrays(mode, 0, count) - - glDisableClientState(GL_VERTEX_ARRAY) - if self.colorData: - glDisableClientState(GL_COLOR_ARRAY) - if self.uvData: - glDisableClientState(GL_TEXTURE_COORD_ARRAY) - - def clear(self): - self.vertices = [] - self.colors = [] - self.uvs = [] - self.data = array.array('f') - self.colorData = None - self.uvData = None \ No newline at end of file diff --git a/archive/editor/maptool.py b/archive/editor/maptool.py deleted file mode 100644 index 4b9a1fe8..00000000 --- a/archive/editor/maptool.py +++ /dev/null @@ -1,143 +0,0 @@ -import os -from PyQt5.QtWidgets import QMainWindow, QWidget, QHBoxLayout, QMessageBox -from PyQt5.QtCore import Qt -from tools.editor.map.glwidget import GLWidget -from tools.editor.map.mapleftpanel import MapLeftPanel -from tools.editor.map.mapinfopanel import MapInfoPanel -from tools.editor.map.menubar import MapMenubar -from tools.editor.map.statusbar import StatusBar -from tools.dusk.map import Map -from tools.dusk.defs import CHUNK_WIDTH, CHUNK_HEIGHT, CHUNK_DEPTH, TILE_SHAPE_NULL, TILE_SHAPE_FLOOR -from tools.editor.map.selectbox import SelectBox -from tools.editor.map.camera import Camera -from tools.editor.map.grid import Grid - -class MapWindow(QMainWindow): - def __init__(self): - super().__init__() - - self.insertPressed = False - self.deletePressed = False - - # Subclasses - self.map = Map(self) - self.camera = Camera(self) - self.grid = Grid() - self.selectBox = SelectBox(self) - - # Window setup - self.setWindowTitle("Dusk Map Editor") - self.resize(1600, 900) - - # Menubar (TESTING) - self.menubar = MapMenubar(self) - - central = QWidget() - self.setCentralWidget(central) - mainLayout = QHBoxLayout(central) - - # Left panel (tabs + nav buttons) - self.leftPanel = MapLeftPanel(self) - self.leftPanel.setFixedWidth(350) - mainLayout.addWidget(self.leftPanel) - - # Center panel (GLWidget + controls) - self.glWidget = GLWidget(self) - mainLayout.addWidget(self.glWidget, stretch=3) - - # Right panel (MapInfoPanel) - self.mapInfoPanel = MapInfoPanel(self) - rightWidget = self.mapInfoPanel - rightWidget.setFixedWidth(250) - mainLayout.addWidget(rightWidget) - - # Status bar - self.statusBar = StatusBar(self) - self.setStatusBar(self.statusBar) - - self.installEventFilter(self) - self.installEventFilterRecursively(self) - - def installEventFilterRecursively(self, widget): - for child in widget.findChildren(QWidget): - child.installEventFilter(self) - self.installEventFilterRecursively(child) - - def closeEvent(self, event): - if not self.map.isDirty(): - event.accept() - return - - reply = QMessageBox.question( - self, - "Unsaved Changes", - "You have unsaved changes. Do you want to save before closing?", - QMessageBox.Save | QMessageBox.Discard | QMessageBox.Cancel, - QMessageBox.Save - ) - if reply == QMessageBox.Save: - self.map.save() - elif reply == QMessageBox.Cancel: - event.ignore() - return - event.accept() - - def eventFilter(self, obj, event): - if event.type() == event.KeyPress: - amtX, amtY, amtZ = 0, 0, 0 - - key = event.key() - if key == Qt.Key_Left: - amtX = -1 - elif key == Qt.Key_Right: - amtX = 1 - elif key == Qt.Key_Up: - amtY = -1 - elif key == Qt.Key_Down: - amtY = 1 - elif key == Qt.Key_PageUp: - amtZ = 1 - elif key == Qt.Key_PageDown: - amtZ = -1 - - if event.modifiers() & Qt.ShiftModifier: - amtX *= CHUNK_WIDTH - amtY *= CHUNK_HEIGHT - amtZ *= CHUNK_DEPTH - - if amtX != 0 or amtY != 0 or amtZ != 0: - self.map.moveRelative(amtX, amtY, amtZ) - if self.insertPressed: - tile = self.map.getTileAtWorldPos(*self.map.position) - if tile is not None and tile.shape == TILE_SHAPE_NULL: - tile.setShape(TILE_SHAPE_FLOOR) - if self.deletePressed: - tile = self.map.getTileAtWorldPos(*self.map.position) - if tile is not None: - tile.setShape(TILE_SHAPE_NULL) - event.accept() - return True - - if key == Qt.Key_Delete: - tile = self.map.getTileAtWorldPos(*self.map.position) - self.deletePressed = True - if tile is not None: - tile.setShape(TILE_SHAPE_NULL) - event.accept() - return True - if key == Qt.Key_Insert: - tile = self.map.getTileAtWorldPos(*self.map.position) - self.insertPressed = True - if tile is not None and tile.shape == TILE_SHAPE_NULL: - tile.setShape(TILE_SHAPE_FLOOR) - event.accept() - elif event.type() == event.KeyRelease: - key = event.key() - if key == Qt.Key_Delete: - self.deletePressed = False - event.accept() - return True - if key == Qt.Key_Insert: - self.insertPressed = False - event.accept() - return super().eventFilter(obj, event) \ No newline at end of file diff --git a/archive/loader/CMakeLists.txt b/archive/loader/CMakeLists.txt deleted file mode 100644 index 638a677d..00000000 --- a/archive/loader/CMakeLists.txt +++ /dev/null @@ -1,13 +0,0 @@ -# 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 - assettexture.c - assettileset.c - assetlanguage.c - assetscript.c -) \ No newline at end of file diff --git a/archive/loader/assetlanguage.c b/archive/loader/assetlanguage.c deleted file mode 100644 index b895a004..00000000 --- a/archive/loader/assetlanguage.c +++ /dev/null @@ -1,45 +0,0 @@ -/** - * Copyright (c) 2025 Dominic Masters - * - * This software is released under the MIT License. - * https://opensource.org/licenses/MIT - */ - -#include "asset/asset.h" -#include "assert/assert.h" -#include "locale/localemanager.h" - -errorret_t assetLanguageHandler(assetcustom_t custom) { - assertNotNull(custom.zipFile, "Custom asset zip file cannot be NULL"); - assertNotNull(custom.output, "Custom asset output cannot be NULL"); - - assetlanguage_t *lang = (assetlanguage_t *)custom.output; - errorChain(assetLanguageInit(lang, custom.zipFile)); - - errorOk(); -} - -errorret_t assetLanguageInit( - assetlanguage_t *lang, - zip_file_t *zipFile -) { - errorThrow("Language asset initialization is not yet implemented."); -} - -errorret_t assetLanguageRead( - assetlanguage_t *lang, - const uint32_t key, - char_t *buffer, - const uint32_t bufferSize, - uint32_t *outLength -) { - errorThrow("Language string reading is not yet implemented."); -} - -void assetLanguageDispose(assetlanguage_t *lang) { - assertNotNull(lang, "Language asset cannot be NULL"); - - if(lang->zip) { - zip_fclose(lang->zip); - } -} \ No newline at end of file diff --git a/archive/loader/assetlanguage.h b/archive/loader/assetlanguage.h deleted file mode 100644 index b6a099ec..00000000 --- a/archive/loader/assetlanguage.h +++ /dev/null @@ -1,61 +0,0 @@ -/** - * Copyright (c) 2025 Dominic Masters - * - * This software is released under the MIT License. - * https://opensource.org/licenses/MIT - */ - -#pragma once -#include "error/error.h" -#include "duskdefs.h" -#include - -typedef struct { - zip_file_t *zip; - zip_int64_t chunksOffset; -} assetlanguage_t; - -typedef struct assetcustom_s assetcustom_t; - -/** - * Receiving function from the asset manager to handle language assets. - * - * @param custom Custom asset loading data. - * @return Error code. - */ -errorret_t assetLanguageHandler(assetcustom_t custom); - -/** - * Initializes a language asset and loads the header data into memory. - * - * @param lang Language asset to initialize. - * @param zipFile Zip file handle for the language asset. - * @return Error code. - */ -errorret_t assetLanguageInit(assetlanguage_t *lang, zip_file_t *zipFile); - -/** - * Reads a string from the language asset into the provided buffer. - * - * @param lang Language asset to read from. - * @param key Language key to read. - * @param buffer Buffer to read the string into. - * @param bufferSize Size of the provided buffer. - * @param outLength Pointer to store the length of the string read. - * @return Error code. - */ -errorret_t assetLanguageRead( - assetlanguage_t *lang, - const uint32_t key, - char_t *buffer, - const uint32_t bufferSize, - uint32_t *outLength -); - -/** - * Disposes of language asset resources. - * - * @param custom Custom asset loading data. - * @return Error code. - */ -void assetLanguageDispose(assetlanguage_t *lang); \ No newline at end of file diff --git a/archive/loader/assetscript.c b/archive/loader/assetscript.c deleted file mode 100644 index 90772257..00000000 --- a/archive/loader/assetscript.c +++ /dev/null @@ -1,58 +0,0 @@ -/** - * Copyright (c) 2025 Dominic Masters - * - * This software is released under the MIT License. - * https://opensource.org/licenses/MIT - */ - -#include "asset/asset.h" -#include "assert/assert.h" - -errorret_t assetScriptHandler(assetcustom_t custom) { - assertNotNull(custom.zipFile, "Custom asset zip file cannot be NULL"); - assertNotNull(custom.output, "Custom asset output cannot be NULL"); - - assetscript_t *script = (assetscript_t *)custom.output; - errorChain(assetScriptInit(script, custom.zipFile)); - - errorOk(); -} - -errorret_t assetScriptInit( - assetscript_t *script, - zip_file_t *zipFile -) { - assertNotNull(script, "Script asset cannot be NULL"); - assertNotNull(zipFile, "Zip file cannot be NULL"); - - // We now own the zip file handle. - script->zip = zipFile; - - errorOk(); -} - -const char_t * assetScriptReader(lua_State* lState, void* data, size_t* size) { - assetscript_t *script = (assetscript_t *)data; - zip_int64_t bytesRead = zip_fread( - script->zip, script->buffer, sizeof(script->buffer) - ); - - if(bytesRead < 0) { - *size = 0; - return NULL; - } - - *size = (size_t)bytesRead; - return script->buffer; -} - -errorret_t assetScriptDispose(assetscript_t *script) { - assertNotNull(script, "Script asset cannot be NULL"); - - if(script->zip != NULL) { - zip_fclose(script->zip); - script->zip = NULL; - } - - errorOk(); -} \ No newline at end of file diff --git a/archive/loader/assetscript.h b/archive/loader/assetscript.h deleted file mode 100644 index 38b762a9..00000000 --- a/archive/loader/assetscript.h +++ /dev/null @@ -1,57 +0,0 @@ -/** - * Copyright (c) 2025 Dominic Masters - * - * This software is released under the MIT License. - * https://opensource.org/licenses/MIT - */ - -#pragma once -#include "error/error.h" -#include "duskdefs.h" -#include -#include "script/scriptcontext.h" - -#define ASSET_SCRIPT_BUFFER_SIZE 1024 - -typedef struct assetscript_s { - zip_file_t *zip; - char_t buffer[ASSET_SCRIPT_BUFFER_SIZE]; -} assetscript_t; - -typedef struct assetcustom_s assetcustom_t; - -/** - * Receiving function from the asset manager to handle script assets. - * - * @param custom Custom asset loading data. - * @return Error code. - */ -errorret_t assetScriptHandler(assetcustom_t custom); - -/** - * Initializes a script asset. - * - * @param script Script asset to initialize. - * @param zipFile Zip file handle for the script asset. - * @return Error code. - */ -errorret_t assetScriptInit(assetscript_t *script, zip_file_t *zipFile); - -/** - * Reader function for Lua to read script data from the asset. - * - * @param L Lua state. - * @param data Pointer to the assetscript_t structure. - * @param size Pointer to store the size of the read data. - * @return Pointer to the read data buffer. - */ -const char_t * assetScriptReader(lua_State* L, void* data, size_t* size); - -/** - * Disposes of a script asset, freeing any allocated resources. - * - * @param script Script asset to dispose of. - * @return Error code. - */ -errorret_t assetScriptDispose(assetscript_t *script); - diff --git a/archive/loader/assettexture.c b/archive/loader/assettexture.c deleted file mode 100644 index 07bd9d9b..00000000 --- a/archive/loader/assettexture.c +++ /dev/null @@ -1,78 +0,0 @@ -/** - * Copyright (c) 2025 Dominic Masters - * - * This software is released under the MIT License. - * https://opensource.org/licenses/MIT - */ - -#include "assettexture.h" -#include "asset/assettype.h" -#include "assert/assert.h" -#include "display/texture/texture.h" -#include "util/endian.h" - -errorret_t assetTextureLoad(assetentire_t entire) { - assertNotNull(entire.data, "Data pointer cannot be NULL."); - assertNotNull(entire.output, "Output pointer cannot be NULL."); - - assettexture_t *assetData = (assettexture_t *)entire.data; - texture_t *texture = (texture_t *)entire.output; - - // Read header and version (first 4 bytes) - if( - assetData->header[0] != 'D' || - assetData->header[1] != 'T' || - assetData->header[2] != 'X' - ) { - errorThrow("Invalid texture header"); - } - - // Version (can only be 1 atm) - if(assetData->version != 0x01) { - errorThrow("Unsupported texture version"); - } - - // Fix endian - assetData->width = endianLittleToHost32(assetData->width); - assetData->height = endianLittleToHost32(assetData->height); - - // Check dimensions. - if( - assetData->width == 0 || assetData->width > ASSET_TEXTURE_WIDTH_MAX || - assetData->height == 0 || assetData->height > ASSET_TEXTURE_HEIGHT_MAX - ) { - errorThrow("Invalid texture dimensions"); - } - - // Validate format - textureformat_t format; - texturedata_t data; - - switch(assetData->type) { - case 0x00: // RGBA8888 - format = TEXTURE_FORMAT_RGBA; - data.rgbaColors = (color_t *)assetData->data; - break; - - // case 0x01: - // format = TEXTURE_FORMAT_RGB; - // break; - - // case 0x02: - // format = TEXTURE_FORMAT_RGB565; - // break; - - // case 0x03: - // format = TEXTURE_FORMAT_RGB5A3; - // break; - - default: - errorThrow("Unsupported texture format"); - } - - errorChain(textureInit( - texture, assetData->width, assetData->height, format, data - )); - - errorOk(); -} \ No newline at end of file diff --git a/archive/loader/assettexture.h b/archive/loader/assettexture.h deleted file mode 100644 index 1dbdde4a..00000000 --- a/archive/loader/assettexture.h +++ /dev/null @@ -1,39 +0,0 @@ -/** - * Copyright (c) 2025 Dominic Masters - * - * This software is released under the MIT License. - * https://opensource.org/licenses/MIT - */ - -#pragma once -#include "error/error.h" -#include "display/color.h" - -#define ASSET_TEXTURE_WIDTH_MAX 2048 -#define ASSET_TEXTURE_HEIGHT_MAX 2048 -#define ASSET_TEXTURE_SIZE_MAX ( \ - ASSET_TEXTURE_WIDTH_MAX * ASSET_TEXTURE_HEIGHT_MAX \ -) - -typedef struct assetentire_s assetentire_t; - -#pragma pack(push, 1) -typedef struct { - char_t header[3]; - uint8_t version; - uint8_t type; - uint32_t width; - uint32_t height; - uint8_t data[ASSET_TEXTURE_SIZE_MAX * sizeof(color4b_t)]; -} assettexture_t; -#pragma pack(pop) - -/** - * Loads a palettized texture from the given data pointer into the output - * texture. - * - * @param data Pointer to the raw assettexture_t data. - * @param output Pointer to the texture_t to load the image into. - * @return An error code. - */ -errorret_t assetTextureLoad(assetentire_t entire); \ No newline at end of file diff --git a/archive/loader/assettileset.c b/archive/loader/assettileset.c deleted file mode 100644 index ea485e07..00000000 --- a/archive/loader/assettileset.c +++ /dev/null @@ -1,70 +0,0 @@ -/** - * Copyright (c) 2026 Dominic Masters - * - * This software is released under the MIT License. - * https://opensource.org/licenses/MIT - */ - -#include "asset/asset.h" -#include "assert/assert.h" -#include "display/texture/tileset.h" -#include "util/memory.h" -#include "util/endian.h" - -errorret_t assetTilesetLoad(assetentire_t entire) { - assertNotNull(entire.data, "Asset data cannot be null"); - assertNotNull(entire.output, "Asset output cannot be null"); - - assettileset_t *tilesetData = (assettileset_t *)entire.data; - tileset_t *tileset = (tileset_t *)entire.output; - - if( - tilesetData->header[0] != 'D' || - tilesetData->header[1] != 'T' || - tilesetData->header[2] != 'F' - ) { - errorThrow("Invalid tileset header"); - } - - if(tilesetData->version != 0x00) { - errorThrow("Unsupported tileset version"); - } - - // Fix endianness - tilesetData->tileWidth = endianLittleToHost16(tilesetData->tileWidth); - tilesetData->tileHeight = endianLittleToHost16(tilesetData->tileHeight); - tilesetData->columnCount = endianLittleToHost16(tilesetData->columnCount); - tilesetData->rowCount = endianLittleToHost16(tilesetData->rowCount); - tilesetData->right = endianLittleToHost16(tilesetData->right); - tilesetData->bottom = endianLittleToHost16(tilesetData->bottom); - - if(tilesetData->tileWidth == 0) { - errorThrow("Tile width cannot be 0"); - } - if(tilesetData->tileHeight == 0) { - errorThrow("Tile height cannot be 0"); - } - if(tilesetData->columnCount == 0) { - errorThrow("Column count cannot be 0"); - } - if(tilesetData->rowCount == 0) { - errorThrow("Row count cannot be 0"); - } - - tilesetData->u0 = endianLittleToHostFloat(tilesetData->u0); - tilesetData->v0 = endianLittleToHostFloat(tilesetData->v0); - - if(tilesetData->v0 < 0.0f || tilesetData->v0 > 1.0f) { - errorThrow("Invalid v0 value in tileset"); - } - - // Setup tileset - tileset->tileWidth = tilesetData->tileWidth; - tileset->tileHeight = tilesetData->tileHeight; - tileset->tileCount = tilesetData->columnCount * tilesetData->rowCount; - tileset->columns = tilesetData->columnCount; - tileset->rows = tilesetData->rowCount; - tileset->uv[0] = tilesetData->u0; - tileset->uv[1] = tilesetData->v0; - errorOk(); -} \ No newline at end of file diff --git a/archive/loader/assettileset.h b/archive/loader/assettileset.h deleted file mode 100644 index a659feaf..00000000 --- a/archive/loader/assettileset.h +++ /dev/null @@ -1,32 +0,0 @@ -/** - * Copyright (c) 2026 Dominic Masters - * - * This software is released under the MIT License. - * https://opensource.org/licenses/MIT - */ - -#pragma once -#include "error/error.h" - -#pragma pack(push, 1) -typedef struct { - char_t header[3]; - uint8_t version; - uint16_t tileWidth; - uint16_t tileHeight; - uint16_t columnCount; - uint16_t rowCount; - uint16_t right; - uint16_t bottom; - float_t u0; - float_t v0; -} assettileset_t; -#pragma pack(pop) - -/** - * Loads a tileset from the given data pointer into the output tileset. - * - * @param entire Data received from the asset loader system. - * @return An error code. - */ -errorret_t assetTilesetLoad(assetentire_t entire); \ No newline at end of file diff --git a/archive/map/CMakeLists.txt b/archive/map/CMakeLists.txt deleted file mode 100644 index 2665d3f0..00000000 --- a/archive/map/CMakeLists.txt +++ /dev/null @@ -1,6 +0,0 @@ -# Copyright (c) 2025 Dominic Masters -# -# This software is released under the MIT License. -# https://opensource.org/licenses/MIT - -add_subdirectory(testmap) \ No newline at end of file diff --git a/archive/map/testmap/CMakeLists.txt b/archive/map/testmap/CMakeLists.txt deleted file mode 100644 index 98749424..00000000 --- a/archive/map/testmap/CMakeLists.txt +++ /dev/null @@ -1,6 +0,0 @@ -# Copyright (c) 2025 Dominic Masters -# -# This software is released under the MIT License. -# https://opensource.org/licenses/MIT - -add_asset(MAP testmap.json) \ No newline at end of file diff --git a/archive/map/testmap/chunks/-1_-1_0.json b/archive/map/testmap/chunks/-1_-1_0.json deleted file mode 100644 index 7f5f47e9..00000000 --- a/archive/map/testmap/chunks/-1_-1_0.json +++ /dev/null @@ -1 +0,0 @@ -{"shapes": [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], "entities": []} \ No newline at end of file diff --git a/archive/map/testmap/chunks/-1_-1_1.json b/archive/map/testmap/chunks/-1_-1_1.json deleted file mode 100644 index cc2dc76a..00000000 --- a/archive/map/testmap/chunks/-1_-1_1.json +++ /dev/null @@ -1 +0,0 @@ -{"shapes": [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], "entities": []} \ No newline at end of file diff --git a/archive/map/testmap/chunks/-1_0_0.json b/archive/map/testmap/chunks/-1_0_0.json deleted file mode 100644 index e0416962..00000000 --- a/archive/map/testmap/chunks/-1_0_0.json +++ /dev/null @@ -1 +0,0 @@ -{"shapes": [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], "entities": []} \ No newline at end of file diff --git a/archive/map/testmap/chunks/-2_1_0.json b/archive/map/testmap/chunks/-2_1_0.json deleted file mode 100644 index 144f2ae8..00000000 --- a/archive/map/testmap/chunks/-2_1_0.json +++ /dev/null @@ -1 +0,0 @@ -{"shapes": [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], "entities": []} \ No newline at end of file diff --git a/archive/map/testmap/chunks/0_-1_0.json b/archive/map/testmap/chunks/0_-1_0.json deleted file mode 100644 index 470f90ef..00000000 --- a/archive/map/testmap/chunks/0_-1_0.json +++ /dev/null @@ -1 +0,0 @@ -{"shapes": [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 5, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 5, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], "entities": []} \ No newline at end of file diff --git a/archive/map/testmap/chunks/0_-1_1.json b/archive/map/testmap/chunks/0_-1_1.json deleted file mode 100644 index c567855b..00000000 --- a/archive/map/testmap/chunks/0_-1_1.json +++ /dev/null @@ -1 +0,0 @@ -{"shapes": [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 5, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 5, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], "entities": []} \ No newline at end of file diff --git a/archive/map/testmap/chunks/0_0_0.json b/archive/map/testmap/chunks/0_0_0.json deleted file mode 100644 index 3bb86dd8..00000000 --- a/archive/map/testmap/chunks/0_0_0.json +++ /dev/null @@ -1 +0,0 @@ -{"shapes": [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 8, 5, 9, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 3, 1, 4, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 3, 1, 4, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 6, 2, 7, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 5, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], "entities": []} \ No newline at end of file diff --git a/archive/map/testmap/chunks/1_0_0.json b/archive/map/testmap/chunks/1_0_0.json deleted file mode 100644 index 144f2ae8..00000000 --- a/archive/map/testmap/chunks/1_0_0.json +++ /dev/null @@ -1 +0,0 @@ -{"shapes": [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], "entities": []} \ No newline at end of file diff --git a/archive/map/testmap/chunks/2_0_0.json b/archive/map/testmap/chunks/2_0_0.json deleted file mode 100644 index 144f2ae8..00000000 --- a/archive/map/testmap/chunks/2_0_0.json +++ /dev/null @@ -1 +0,0 @@ -{"shapes": [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], "entities": []} \ No newline at end of file diff --git a/archive/map/testmap/chunks/3_0_0.json b/archive/map/testmap/chunks/3_0_0.json deleted file mode 100644 index 144f2ae8..00000000 --- a/archive/map/testmap/chunks/3_0_0.json +++ /dev/null @@ -1 +0,0 @@ -{"shapes": [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], "entities": []} \ No newline at end of file diff --git a/archive/map/testmap/chunks/4_0_0.json b/archive/map/testmap/chunks/4_0_0.json deleted file mode 100644 index 144f2ae8..00000000 --- a/archive/map/testmap/chunks/4_0_0.json +++ /dev/null @@ -1 +0,0 @@ -{"shapes": [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], "entities": []} \ No newline at end of file diff --git a/archive/map/testmap/chunks/5_0_0.json b/archive/map/testmap/chunks/5_0_0.json deleted file mode 100644 index bf6e0560..00000000 --- a/archive/map/testmap/chunks/5_0_0.json +++ /dev/null @@ -1 +0,0 @@ -{"shapes": [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]} \ No newline at end of file diff --git a/archive/map/testmap/testmap.json b/archive/map/testmap/testmap.json deleted file mode 100644 index d9e4030d..00000000 --- a/archive/map/testmap/testmap.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - "name": "Test" -} \ No newline at end of file diff --git a/archive/modulemap.h b/archive/modulemap.h deleted file mode 100644 index 759af4d4..00000000 --- a/archive/modulemap.h +++ /dev/null @@ -1,49 +0,0 @@ -/** - * Copyright (c) 2025 Dominic Masters - * - * This software is released under the MIT License. - * https://opensource.org/licenses/MIT - */ - -#pragma once -#include "script/scriptcontext.h" -#include "debug/debug.h" -#include "assert/assert.h" -#include "rpg/overworld/map.h" - -int moduleMapLoad(lua_State *L) { - assertNotNull(L, "Lua state cannot be NULL"); - - if(!lua_isstring(L, 1)) { - luaL_error(L, "Expected string map filename"); - return 0; - } - - // Potentially provide up to 3 params - chunkpos_t initial = { .x = 0, .y = 0, .z = 0 }; - if(lua_isnumber(L, 2)) { - initial.x = (chunkunit_t)lua_tonumber(L, 2); - } - if(lua_isnumber(L, 3)) { - initial.y = (chunkunit_t)lua_tonumber(L, 3); - } - if(lua_isnumber(L, 4)) { - initial.z = (chunkunit_t)lua_tonumber(L, 4); - } - - // Load the map. - errorret_t ret = mapLoad(luaL_checkstring(L, 1), initial); - if(ret.code != ERROR_OK) { - luaL_error(L, "Failed to load map"); - errorCatch(errorPrint(ret)); - return 0; - } - - return 0; -} - -void moduleMapSystem(scriptcontext_t *context) { - assertNotNull(context, "Script context cannot be NULL"); - - lua_register(context->luaState, "mapLoad", moduleMapLoad); -} \ No newline at end of file diff --git a/archive/platform/CMakeLists.txt b/archive/platform/CMakeLists.txt deleted file mode 100644 index b1563553..00000000 --- a/archive/platform/CMakeLists.txt +++ /dev/null @@ -1,12 +0,0 @@ -# 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 - sdl2.c - psp.c - dolphin.c -) \ No newline at end of file diff --git a/archive/platform/dolphin.c b/archive/platform/dolphin.c deleted file mode 100644 index b21593b9..00000000 --- a/archive/platform/dolphin.c +++ /dev/null @@ -1,92 +0,0 @@ -/** - * Copyright (c) 2026 Dominic Masters - * - * This software is released under the MIT License. - * https://opensource.org/licenses/MIT - */ - -#include "dolphin.h" - -void displayInitDolphin(void) { - VIDEO_Init(); - DISPLAY.screenMode = VIDEO_GetPreferredMode(NULL); - DISPLAY.frameBuffer[0] = MEM_K0_TO_K1( - SYS_AllocateFramebuffer(DISPLAY.screenMode) - ); - DISPLAY.frameBuffer[1] = MEM_K0_TO_K1( - SYS_AllocateFramebuffer(DISPLAY.screenMode) - ); - VIDEO_Configure(DISPLAY.screenMode); - - VIDEO_SetNextFramebuffer(DISPLAY.frameBuffer[DISPLAY.whichFrameBuffer]); - // VIDEO_SetPostRetraceCallback(copy_buffers); - VIDEO_SetBlack(FALSE); - VIDEO_Flush(); - VIDEO_WaitVSync(); - if(DISPLAY.screenMode->viTVMode & VI_NON_INTERLACE) VIDEO_WaitVSync(); - - DISPLAY.fifoBuffer = memalign(32, DISPLAY_FIFO_SIZE); - memoryZero(DISPLAY.fifoBuffer, DISPLAY_FIFO_SIZE); - - GX_Init(DISPLAY.fifoBuffer, DISPLAY_FIFO_SIZE); - - // This seems to be mostly related to interlacing vs progressive - GX_SetViewport( - 0, 0, - DISPLAY.screenMode->fbWidth, DISPLAY.screenMode->efbHeight, - 0, 1 - ); - float_t yscale = GX_GetYScaleFactor( - DISPLAY.screenMode->efbHeight, DISPLAY.screenMode->xfbHeight - ); - uint32_t xfbHeight = GX_SetDispCopyYScale(yscale); - GX_SetScissor( - 0, 0, - DISPLAY.screenMode->fbWidth, DISPLAY.screenMode->efbHeight - ); - GX_SetDispCopySrc( - 0, 0, - DISPLAY.screenMode->fbWidth, DISPLAY.screenMode->efbHeight - ); - GX_SetDispCopyDst(DISPLAY.screenMode->fbWidth, xfbHeight); - GX_SetCopyFilter( - DISPLAY.screenMode->aa, - DISPLAY.screenMode->sample_pattern, - GX_TRUE, - DISPLAY.screenMode->vfilter - ); - GX_SetFieldMode( - DISPLAY.screenMode->field_rendering, - ( - (DISPLAY.screenMode->viHeight == 2 * DISPLAY.screenMode->xfbHeight) ? - GX_ENABLE : - GX_DISABLE - ) - ); - - // Setup cull modes - GX_SetCullMode(GX_CULL_NONE); - GX_SetZMode(GX_FALSE, GX_ALWAYS, GX_FALSE); - GX_CopyDisp(DISPLAY.frameBuffer[DISPLAY.whichFrameBuffer], GX_TRUE); - GX_SetDispCopyGamma(GX_GM_1_0); - - GX_ClearVtxDesc(); - GX_SetVtxDesc(GX_VA_POS, GX_INDEX16); - GX_SetVtxDesc(GX_VA_CLR0, GX_INDEX16); - GX_SetVtxDesc(GX_VA_TEX0, GX_INDEX16); - GX_SetVtxAttrFmt(GX_VTXFMT0, GX_VA_POS, GX_POS_XYZ, GX_F32, 0); - GX_SetVtxAttrFmt(GX_VTXFMT0, GX_VA_CLR0, GX_CLR_RGBA, GX_U8, 0); - GX_SetVtxAttrFmt(GX_VTXFMT0, GX_VA_TEX0, GX_TEX_ST, GX_F32, 0); -} - -void displayDolphinSwap(void) { - GX_DrawDone(); - - DISPLAY.whichFrameBuffer ^= 1; - GX_SetZMode(GX_TRUE, GX_LEQUAL, GX_TRUE); - GX_SetColorUpdate(GX_TRUE); - GX_CopyDisp(DISPLAY.frameBuffer[DISPLAY.whichFrameBuffer], GX_TRUE); - VIDEO_SetNextFramebuffer(DISPLAY.frameBuffer[DISPLAY.whichFrameBuffer]); - VIDEO_Flush(); - VIDEO_WaitVSync(); -} \ No newline at end of file diff --git a/archive/platform/dolphin.h b/archive/platform/dolphin.h deleted file mode 100644 index c62fa287..00000000 --- a/archive/platform/dolphin.h +++ /dev/null @@ -1,27 +0,0 @@ -/** - * Copyright (c) 2026 Dominic Masters - * - * This software is released under the MIT License. - * https://opensource.org/licenses/MIT - */ - -#pragma once -#include "dusk.h" -#include "display/displaydefs.h" - -typedef struct { - void *frameBuffer[2];// Double-Bufferred - int whichFrameBuffer; - GXRModeObj *screenMode; - void *fifoBuffer; -} displaydolphin_t; - -/** - * Initializes the display for Dolphin. - */ -void displayDolphinInit(void); - -/** - * Swaps the back buffer to the front for Dolphin. - */ -void displayDolphinSwap(void); \ No newline at end of file diff --git a/archive/platform/psp.c b/archive/platform/psp.c deleted file mode 100644 index 8fe9e216..00000000 --- a/archive/platform/psp.c +++ /dev/null @@ -1,12 +0,0 @@ -/** - * Copyright (c) 2026 Dominic Masters - * - * This software is released under the MIT License. - * https://opensource.org/licenses/MIT - */ - -#include "psp.h" - -void displayInitPSP(void) { - DISPLAY.usingShaderedPalettes = false; -} \ No newline at end of file diff --git a/archive/platform/psp.h b/archive/platform/psp.h deleted file mode 100644 index d822b2fa..00000000 --- a/archive/platform/psp.h +++ /dev/null @@ -1,13 +0,0 @@ -/** - * Copyright (c) 2026 Dominic Masters - * - * This software is released under the MIT License. - * https://opensource.org/licenses/MIT - */ - -#pragma once - -/** - * Initializes the display for PSP. - */ -void displayInitPSP(void); \ No newline at end of file diff --git a/archive/platform/sdl2.c b/archive/platform/sdl2.c deleted file mode 100644 index f1e09630..00000000 --- a/archive/platform/sdl2.c +++ /dev/null @@ -1,32 +0,0 @@ -/** - * Copyright (c) 2026 Dominic Masters - * - * This software is released under the MIT License. - * https://opensource.org/licenses/MIT - */ - -#include "sdl2.h" - -void displaySDL2Update(void) { -} - -void displaySDL2Swap(void) { - SDL_GL_SwapWindow(DISPLAY.window); - - GLenum err; - while((err = glGetError()) != GL_NO_ERROR) { - debugPrint("GL Error: %d\n", err); - } -} - -void displaySDL2Dispose(void) { - if(DISPLAY.glContext) { - SDL_GL_DeleteContext(DISPLAY.glContext); - DISPLAY.glContext = NULL; - } - if(DISPLAY.window) { - SDL_DestroyWindow(DISPLAY.window); - DISPLAY.window = NULL; - } - SDL_Quit(); -} \ No newline at end of file diff --git a/archive/platform/sdl2.h b/archive/platform/sdl2.h deleted file mode 100644 index 83ec5715..00000000 --- a/archive/platform/sdl2.h +++ /dev/null @@ -1,35 +0,0 @@ -/** - * Copyright (c) 2026 Dominic Masters - * - * This software is released under the MIT License. - * https://opensource.org/licenses/MIT - */ - -#pragma once -#include "dusk.h" - -typedef struct { - SDL_Window *window; - SDL_GLContext glContext; - bool_t usingShaderedPalettes; -} displaysdl2_t; - -/** - * Initializes the display for SDL2. - */ -void displaySDL2Init(void); - -/** - * Updates the display for SDL2. - */ -void displaySDL2Update(void); - -/** - * Swaps the display buffers for SDL2. - */ -void displaySDL2Swap(void); - -/** - * Disposes of the display for SDL2. - */ -void displaySDL2Dispose(void); \ No newline at end of file diff --git a/archive/rpg/CMakeLists.txt b/archive/rpg/CMakeLists.txt deleted file mode 100644 index cb964fdb..00000000 --- a/archive/rpg/CMakeLists.txt +++ /dev/null @@ -1,17 +0,0 @@ -# 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 - rpg.c - rpgcamera.c - rpgtextbox.c -) - -# Subdirs -add_subdirectory(cutscene) -add_subdirectory(entity) -add_subdirectory(overworld) \ No newline at end of file diff --git a/archive/rpg/cutscene/CMakeLists.txt b/archive/rpg/cutscene/CMakeLists.txt deleted file mode 100644 index 9c927691..00000000 --- a/archive/rpg/cutscene/CMakeLists.txt +++ /dev/null @@ -1,14 +0,0 @@ -# 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 - cutscenesystem.c - cutscenemode.c -) - -# Subdirs -add_subdirectory(item) \ No newline at end of file diff --git a/archive/rpg/cutscene/cutscene.h b/archive/rpg/cutscene/cutscene.h deleted file mode 100644 index 7df18ad6..00000000 --- a/archive/rpg/cutscene/cutscene.h +++ /dev/null @@ -1,14 +0,0 @@ -/** - * Copyright (c) 2025 Dominic Masters - * - * This software is released under the MIT License. - * https://opensource.org/licenses/MIT - */ - -#pragma once -#include "rpg/cutscene/item/cutsceneitem.h" - -typedef struct cutscene_s { - const cutsceneitem_t *items; - uint8_t itemCount; -} cutscene_t; \ No newline at end of file diff --git a/archive/rpg/cutscene/cutscenemode.c b/archive/rpg/cutscene/cutscenemode.c deleted file mode 100644 index 39c10b3e..00000000 --- a/archive/rpg/cutscene/cutscenemode.c +++ /dev/null @@ -1,19 +0,0 @@ -/** - * Copyright (c) 2025 Dominic Masters - * - * This software is released under the MIT License. - * https://opensource.org/licenses/MIT - */ - -#include "rpg/cutscene/cutscenesystem.h" - -bool_t cutsceneModeIsInputAllowed() { - switch(CUTSCENE_SYSTEM.mode) { - case CUTSCENE_MODE_FULL_FREEZE: - case CUTSCENE_MODE_INPUT_FREEZE: - return false; - - default: - return true; - } -} \ No newline at end of file diff --git a/archive/rpg/cutscene/cutscenemode.h b/archive/rpg/cutscene/cutscenemode.h deleted file mode 100644 index cf20a3d3..00000000 --- a/archive/rpg/cutscene/cutscenemode.h +++ /dev/null @@ -1,26 +0,0 @@ -/** - * Copyright (c) 2025 Dominic Masters - * - * This software is released under the MIT License. - * https://opensource.org/licenses/MIT - */ - -#pragma once -#include "dusk.h" - -typedef enum { - CUTSCENE_MODE_NONE, - CUTSCENE_MODE_FULL_FREEZE, - CUTSCENE_MODE_INPUT_FREEZE, - CUTSCENE_MODE_GAMEPLAY -} cutscenemode_t; - -// Default mode for all cutscenes. -#define CUTSCENE_MODE_INITIAL CUTSCENE_MODE_INPUT_FREEZE - -/** - * Check if input is allowed in the current cutscene mode. - * - * @return true if input is allowed, false otherwise. - */ -bool_t cutsceneModeIsInputAllowed(); \ No newline at end of file diff --git a/archive/rpg/cutscene/cutscenesystem.c b/archive/rpg/cutscene/cutscenesystem.c deleted file mode 100644 index d803e05d..00000000 --- a/archive/rpg/cutscene/cutscenesystem.c +++ /dev/null @@ -1,56 +0,0 @@ -/** - * Copyright (c) 2025 Dominic Masters - * - * This software is released under the MIT License. - * https://opensource.org/licenses/MIT - */ - -#include "cutscenesystem.h" -#include "util/memory.h" - -cutscenesystem_t CUTSCENE_SYSTEM; - -void cutsceneSystemInit() { - memoryZero(&CUTSCENE_SYSTEM, sizeof(cutscenesystem_t)); -} - -void cutsceneSystemStartCutscene(const cutscene_t *cutscene) { - CUTSCENE_SYSTEM.scene = cutscene; - CUTSCENE_SYSTEM.mode = CUTSCENE_MODE_INITIAL; - CUTSCENE_SYSTEM.currentItem = 0xFF;// Set to 0xFF so start wraps. - cutsceneSystemNext(); -} - -void cutsceneSystemUpdate() { - if(CUTSCENE_SYSTEM.scene == NULL) return; - - const cutsceneitem_t *item = cutsceneSystemGetCurrentItem(); - cutsceneItemUpdate(item, &CUTSCENE_SYSTEM.data); -} - -void cutsceneSystemNext() { - if(CUTSCENE_SYSTEM.scene == NULL) return; - - CUTSCENE_SYSTEM.currentItem++; - - // End of the cutscene? - if( - CUTSCENE_SYSTEM.currentItem >= CUTSCENE_SYSTEM.scene->itemCount - ) { - CUTSCENE_SYSTEM.scene = NULL; - CUTSCENE_SYSTEM.currentItem = 0xFF; - CUTSCENE_SYSTEM.mode = CUTSCENE_MODE_NONE; - return; - } - - // Start item. - const cutsceneitem_t *item = cutsceneSystemGetCurrentItem(); - memset(&CUTSCENE_SYSTEM.data, 0, sizeof(CUTSCENE_SYSTEM.data)); - cutsceneItemStart(item, &CUTSCENE_SYSTEM.data); -} - -const cutsceneitem_t * cutsceneSystemGetCurrentItem() { - if(CUTSCENE_SYSTEM.scene == NULL) return NULL; - - return &CUTSCENE_SYSTEM.scene->items[CUTSCENE_SYSTEM.currentItem]; -} \ No newline at end of file diff --git a/archive/rpg/cutscene/cutscenesystem.h b/archive/rpg/cutscene/cutscenesystem.h deleted file mode 100644 index 7c3242a9..00000000 --- a/archive/rpg/cutscene/cutscenesystem.h +++ /dev/null @@ -1,50 +0,0 @@ -/** - * Copyright (c) 2025 Dominic Masters - * - * This software is released under the MIT License. - * https://opensource.org/licenses/MIT - */ - -#pragma once -#include "cutscene.h" -#include "cutscenemode.h" - -typedef struct { - const cutscene_t *scene; - uint8_t currentItem; - - // Data (used by the current item). - cutsceneitemdata_t data; - cutscenemode_t mode; -} cutscenesystem_t; - -extern cutscenesystem_t CUTSCENE_SYSTEM; - -/** - * Initialize the cutscene system. - */ -void cutsceneSystemInit(); - -/** - * Start a cutscene. - * - * @param cutscene Pointer to the cutscene to start. - */ -void cutsceneSystemStartCutscene(const cutscene_t *cutscene); - -/** - * Advance to the next item in the cutscene. - */ -void cutsceneSystemNext(); - -/** - * Update the cutscene system for one frame. - */ -void cutsceneSystemUpdate(); - -/** - * Get the current cutscene item. - * - * @return Pointer to the current cutscene item. - */ -const cutsceneitem_t * cutsceneSystemGetCurrentItem(); \ No newline at end of file diff --git a/archive/rpg/cutscene/item/CMakeLists.txt b/archive/rpg/cutscene/item/CMakeLists.txt deleted file mode 100755 index 6b3e271a..00000000 --- a/archive/rpg/cutscene/item/CMakeLists.txt +++ /dev/null @@ -1,10 +0,0 @@ -# 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 - cutsceneitem.c -) \ No newline at end of file diff --git a/archive/rpg/cutscene/item/cutscenecallback.h b/archive/rpg/cutscene/item/cutscenecallback.h deleted file mode 100644 index 494f877d..00000000 --- a/archive/rpg/cutscene/item/cutscenecallback.h +++ /dev/null @@ -1,11 +0,0 @@ -/** - * Copyright (c) 2025 Dominic Masters - * - * This software is released under the MIT License. - * https://opensource.org/licenses/MIT - */ - -#pragma once -#include "dusk.h" - -typedef void (*cutscenecallback_t)(void); \ No newline at end of file diff --git a/archive/rpg/cutscene/item/cutsceneitem.c b/archive/rpg/cutscene/item/cutsceneitem.c deleted file mode 100644 index 18a8b582..00000000 --- a/archive/rpg/cutscene/item/cutsceneitem.c +++ /dev/null @@ -1,54 +0,0 @@ -/** - * Copyright (c) 2025 Dominic Masters - * - * This software is released under the MIT License. - * https://opensource.org/licenses/MIT - */ - -#include "rpg/cutscene/cutscenesystem.h" -#include "input/input.h" -#include "time/time.h" - -void cutsceneItemStart(const cutsceneitem_t *item, cutsceneitemdata_t *data) { - switch(item->type) { - case CUTSCENE_ITEM_TYPE_TEXT: { - rpgTextboxShow( - item->text.position, - item->text.text - ); - break; - } - - case CUTSCENE_ITEM_TYPE_WAIT: - data->wait = item->wait; - break; - - case CUTSCENE_ITEM_TYPE_CALLBACK: - if(item->callback != NULL) item->callback(); - break; - - case CUTSCENE_ITEM_TYPE_CUTSCENE: - if(item->cutscene != NULL) cutsceneSystemStartCutscene(item->cutscene); - break; - - default: - break; - } -} - -void cutsceneItemUpdate(const cutsceneitem_t *item, cutsceneitemdata_t *data) { - switch(item->type) { - case CUTSCENE_ITEM_TYPE_TEXT: - if(rpgTextboxIsVisible()) return; - cutsceneSystemNext(); - break; - - case CUTSCENE_ITEM_TYPE_WAIT: - data->wait -= TIME.delta; - if(data->wait <= 0) cutsceneSystemNext(); - break; - - default: - break; - } -} \ No newline at end of file diff --git a/archive/rpg/cutscene/item/cutsceneitem.h b/archive/rpg/cutscene/item/cutsceneitem.h deleted file mode 100644 index f632518d..00000000 --- a/archive/rpg/cutscene/item/cutsceneitem.h +++ /dev/null @@ -1,53 +0,0 @@ -/** - * Copyright (c) 2025 Dominic Masters - * - * This software is released under the MIT License. - * https://opensource.org/licenses/MIT - */ - -#pragma once -#include "cutscenewait.h" -#include "cutscenecallback.h" -#include "cutscenetext.h" - -typedef struct cutscene_s cutscene_t; - -typedef enum { - CUTSCENE_ITEM_TYPE_NULL, - CUTSCENE_ITEM_TYPE_TEXT, - CUTSCENE_ITEM_TYPE_CALLBACK, - CUTSCENE_ITEM_TYPE_WAIT, - CUTSCENE_ITEM_TYPE_CUTSCENE -} cutsceneitemtype_t; - -typedef struct cutsceneitem_s { - cutsceneitemtype_t type; - - // Arguments/Data that will be used when this item is invoked. - union { - cutscenetext_t text; - cutscenecallback_t callback; - cutscenewait_t wait; - const cutscene_t *cutscene; - }; -} cutsceneitem_t; - -typedef union { - cutscenewaitdata_t wait; -} cutsceneitemdata_t; - -/** - * Start the given cutscene item. - * - * @param item The cutscene item to start. - * @param data The cutscene item data storage. - */ -void cutsceneItemStart(const cutsceneitem_t *item, cutsceneitemdata_t *data); - -/** - * Tick the given cutscene item (one frame). - * - * @param item The cutscene item to tick. - * @param data The cutscene item data storage. - */ -void cutsceneItemUpdate(const cutsceneitem_t *item, cutsceneitemdata_t *data); \ No newline at end of file diff --git a/archive/rpg/cutscene/item/cutscenetext.h b/archive/rpg/cutscene/item/cutscenetext.h deleted file mode 100644 index 3ad07f29..00000000 --- a/archive/rpg/cutscene/item/cutscenetext.h +++ /dev/null @@ -1,14 +0,0 @@ -/** - * Copyright (c) 2025 Dominic Masters - * - * This software is released under the MIT License. - * https://opensource.org/licenses/MIT - */ - -#pragma once -#include "rpg/rpgtextbox.h" - -typedef struct { - char_t text[RPG_TEXTBOX_MAX_CHARS]; - rpgtextboxpos_t position; -} cutscenetext_t; \ No newline at end of file diff --git a/archive/rpg/cutscene/item/cutscenewait.h b/archive/rpg/cutscene/item/cutscenewait.h deleted file mode 100644 index 9d590df2..00000000 --- a/archive/rpg/cutscene/item/cutscenewait.h +++ /dev/null @@ -1,12 +0,0 @@ -/** - * Copyright (c) 2025 Dominic Masters - * - * This software is released under the MIT License. - * https://opensource.org/licenses/MIT - */ - -#pragma once -#include "dusk.h" - -typedef float_t cutscenewait_t; -typedef float_t cutscenewaitdata_t; \ No newline at end of file diff --git a/archive/rpg/cutscene/scene/testcutscene.h b/archive/rpg/cutscene/scene/testcutscene.h deleted file mode 100755 index bcdffb21..00000000 --- a/archive/rpg/cutscene/scene/testcutscene.h +++ /dev/null @@ -1,30 +0,0 @@ -/** - * Copyright (c) 2025 Dominic Masters - * - * This software is released under the MIT License. - * https://opensource.org/licenses/MIT - */ - -#pragma once -#include "rpg/cutscene/cutscenesystem.h" - -static const cutsceneitem_t TEST_CUTSCENE_ONE_ITEMS[] = { - { .type = CUTSCENE_ITEM_TYPE_TEXT, .text = { .text = "This is a test cutscene.", .position = RPG_TEXTBOX_POS_BOTTOM } }, - { .type = CUTSCENE_ITEM_TYPE_WAIT, .wait = 2.0f }, - { .type = CUTSCENE_ITEM_TYPE_TEXT, .text = { .text = "It has multiple lines of text.\nAnd waits in between.", .position = RPG_TEXTBOX_POS_TOP } }, -}; - -static const cutscene_t TEST_CUTSCENE_ONE = { - .items = TEST_CUTSCENE_ONE_ITEMS, - .itemCount = sizeof(TEST_CUTSCENE_ONE_ITEMS) / sizeof(cutsceneitem_t) -}; - -static const cutsceneitem_t TEST_CUTSCENE_TWO_ITEMS[] = { - { .type = CUTSCENE_ITEM_TYPE_WAIT, .wait = 1.0f }, - { .type = CUTSCENE_ITEM_TYPE_CUTSCENE, .cutscene = &TEST_CUTSCENE_ONE }, -}; - -static const cutscene_t TEST_CUTSCENE = { - .items = TEST_CUTSCENE_TWO_ITEMS, - .itemCount = sizeof(TEST_CUTSCENE_TWO_ITEMS) / sizeof(cutsceneitem_t) -}; \ No newline at end of file diff --git a/archive/rpg/entity/CMakeLists.txt b/archive/rpg/entity/CMakeLists.txt deleted file mode 100644 index f6e4cda5..00000000 --- a/archive/rpg/entity/CMakeLists.txt +++ /dev/null @@ -1,14 +0,0 @@ -# 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 - entity.c - entityanim.c - npc.c - player.c - entitydir.c -) \ No newline at end of file diff --git a/archive/rpg/entity/entity.c b/archive/rpg/entity/entity.c deleted file mode 100644 index 194c4135..00000000 --- a/archive/rpg/entity/entity.c +++ /dev/null @@ -1,200 +0,0 @@ -/** - * Copyright (c) 2025 Dominic Masters - * - * This software is released under the MIT License. - * https://opensource.org/licenses/MIT - */ - -#include "entity.h" -#include "assert/assert.h" -#include "util/memory.h" -#include "time/time.h" -#include "util/math.h" -#include "rpg/cutscene/cutscenemode.h" -#include "rpg/overworld/map.h" - -entity_t ENTITIES[ENTITY_COUNT]; - -void entityInit(entity_t *entity, const entitytype_t type) { - assertNotNull(entity, "Entity pointer cannot be NULL"); - assertTrue(type < ENTITY_TYPE_COUNT, "Invalid entity type"); - assertTrue(type != ENTITY_TYPE_NULL, "Cannot have NULL entity type"); - assertTrue( - entity >= ENTITIES && entity < ENTITIES + ENTITY_COUNT, - "Entity pointer is out of bounds" - ); - - memoryZero(entity, sizeof(entity_t)); - entity->id = (uint8_t)(entity - ENTITIES); - entity->type = type; - - if(ENTITY_CALLBACKS[type].init != NULL) ENTITY_CALLBACKS[type].init(entity); -} - -void entityUpdate(entity_t *entity) { - assertNotNull(entity, "Entity pointer cannot be NULL"); - assertTrue(entity->type < ENTITY_TYPE_COUNT, "Invalid entity type"); - assertTrue(entity->type != ENTITY_TYPE_NULL, "Cannot have NULL entity type"); - - // What state is the entity in? - if(entity->animation != ENTITY_ANIM_IDLE) { - // Entity is mid animation, tick it (down). - entity->animTime -= TIME.delta; - if(entity->animTime <= 0) { - entity->animation = ENTITY_ANIM_IDLE; - entity->animTime = 0; - } - return; - } - - // Movement code. - if( - cutsceneModeIsInputAllowed() && - ENTITY_CALLBACKS[entity->type].movement != NULL - ) { - ENTITY_CALLBACKS[entity->type].movement(entity); - } -} - -void entityTurn(entity_t *entity, const entitydir_t direction) { - entity->direction = direction; - entity->animation = ENTITY_ANIM_TURN; - entity->animTime = ENTITY_ANIM_TURN_DURATION; -} - -void entityWalk(entity_t *entity, const entitydir_t direction) { - // TODO: Animation, delay, etc. - entity->direction = direction; - - // Where are we moving? - worldpos_t newPos = entity->position; - { - worldunits_t relX, relY; - entityDirGetRelative(direction, &relX, &relY); - newPos.x += relX; - newPos.y += relY; - } - - // Get tile under foot - tile_t tileCurrent = mapGetTile(entity->position); - tile_t tileNew = mapGetTile(newPos); - bool_t fall = false; - bool_t raise = false; - - // Are we walking up a ramp? - if( - tileIsRamp(tileCurrent) && - ( - // Can only walk UP the direction the ramp faces. - (direction+TILE_SHAPE_RAMP_SOUTH) == tileCurrent || - // If diagonal ramp, can go up one of two ways only. - ( - ( - tileCurrent == TILE_SHAPE_RAMP_SOUTHEAST && - (direction == ENTITY_DIR_SOUTH || direction == ENTITY_DIR_EAST) - ) || - ( - tileCurrent == TILE_SHAPE_RAMP_SOUTHWEST && - (direction == ENTITY_DIR_SOUTH || direction == ENTITY_DIR_WEST) - ) || - ( - tileCurrent == TILE_SHAPE_RAMP_NORTHEAST && - (direction == ENTITY_DIR_NORTH || direction == ENTITY_DIR_EAST) - ) || - ( - tileCurrent == TILE_SHAPE_RAMP_NORTHWEST && - (direction == ENTITY_DIR_NORTH || direction == ENTITY_DIR_WEST) - ) - ) - // Must be able to walk up. - ) - ) { - tileNew = TILE_SHAPE_NULL;// Force check for ramp above. - worldpos_t abovePos = newPos; - abovePos.z += 1; - tile_t tileAbove = mapGetTile(abovePos); - - if(tileAbove != TILE_SHAPE_NULL && tileIsWalkable(tileAbove)) { - // We can go up the ramp. - raise = true; - } - } else if(tileNew == TILE_SHAPE_NULL && newPos.z > 0) { - // Falling down? - worldpos_t belowPos = newPos; - belowPos.z -= 1; - tile_t tileBelow = mapGetTile(belowPos); - if( - tileBelow != TILE_SHAPE_NULL && - tileIsRamp(tileBelow) && - ( - // This handles regular cardinal ramps - (entityDirGetOpposite(direction)+TILE_SHAPE_RAMP_SOUTH) == tileBelow || - // This handles diagonal ramps - ( - ( - tileBelow == TILE_SHAPE_RAMP_SOUTHEAST && - (direction == ENTITY_DIR_NORTH || direction == ENTITY_DIR_WEST) - ) || - ( - tileBelow == TILE_SHAPE_RAMP_SOUTHWEST && - (direction == ENTITY_DIR_NORTH || direction == ENTITY_DIR_EAST) - ) || - ( - tileBelow == TILE_SHAPE_RAMP_NORTHEAST && - (direction == ENTITY_DIR_SOUTH || direction == ENTITY_DIR_WEST) - ) || - ( - tileBelow == TILE_SHAPE_RAMP_NORTHWEST && - (direction == ENTITY_DIR_SOUTH || direction == ENTITY_DIR_EAST) - ) - ) - ) - ) { - // We will fall to this tile. - fall = true; - } - } - - // Can we walk here? - if(!raise && !fall && !tileIsWalkable(tileNew)) return;// Blocked - - // Entity in way? - entity_t *other = ENTITIES; - do { - if(other == entity) continue; - if(other->type == ENTITY_TYPE_NULL) continue; - if(!worldPosIsEqual(other->position, newPos)) continue; - return;// Blocked - } while(++other, other < &ENTITIES[ENTITY_COUNT]); - - entity->lastPosition = entity->position; - entity->position = newPos; - entity->animation = ENTITY_ANIM_WALK; - entity->animTime = ENTITY_ANIM_WALK_DURATION;// TODO: Running vs walking - - if(raise) { - entity->position.z += 1; - } else if(fall) { - entity->position.z -= 1; - } -} - -entity_t * entityGetAt(const worldpos_t position) { - entity_t *ent = ENTITIES; - do { - if(ent->type == ENTITY_TYPE_NULL) continue; - if(!worldPosIsEqual(ent->position, position)) continue; - return ent; - } while(++ent, ent < &ENTITIES[ENTITY_COUNT]); - - return NULL; -} - -uint8_t entityGetAvailable() { - entity_t *ent = ENTITIES; - do { - if(ent->type == ENTITY_TYPE_NULL) return ent - ENTITIES; - } while(++ent, ent < &ENTITIES[ENTITY_COUNT]); - - return 0xFF; -} \ No newline at end of file diff --git a/archive/rpg/entity/entity.h b/archive/rpg/entity/entity.h deleted file mode 100644 index 258b54f2..00000000 --- a/archive/rpg/entity/entity.h +++ /dev/null @@ -1,77 +0,0 @@ -/** - * Copyright (c) 2025 Dominic Masters - * - * This software is released under the MIT License. - * https://opensource.org/licenses/MIT - */ - -#pragma once -#include "entitydir.h" -#include "entityanim.h" -#include "entitytype.h" -#include "npc.h" - -typedef struct map_s map_t; - -typedef struct entity_s { - uint8_t id; - entitytype_t type; - entitytypedata_t data; - - // Movement - entitydir_t direction; - worldpos_t position; - worldpos_t lastPosition; - - entityanim_t animation; - float_t animTime; -} entity_t; - -extern entity_t ENTITIES[ENTITY_COUNT]; - -/** - * Initializes an entity structure. - * - * @param entity Pointer to the entity structure to initialize. - * @param type The type of the entity. - */ -void entityInit(entity_t *entity, const entitytype_t type); - -/** - * Updates an entity. - * - * @param entity Pointer to the entity structure to update. - */ -void entityUpdate(entity_t *entity); - -/** - * Turn an entity to face a new direction. - * - * @param entity Pointer to the entity to turn. - * @param direction The direction to face. - */ -void entityTurn(entity_t *entity, const entitydir_t direction); - -/** - * Make an entity walk in a direction. - * - * @param entity Pointer to the entity to make walk. - * @param direction The direction to walk in. - */ -void entityWalk(entity_t *entity, const entitydir_t direction); - -/** - * Gets the entity at a specific world position. - * - * @param map Pointer to the map to check. - * @param pos The world position to check. - * @return Pointer to the entity at the position, or NULL if none. - */ -entity_t *entityGetAt(const worldpos_t pos); - -/** - * Gets an available entity index. - * - * @return The index of an available entity, or 0xFF if none are available. - */ -uint8_t entityGetAvailable(); \ No newline at end of file diff --git a/archive/rpg/entity/entityanim.c b/archive/rpg/entity/entityanim.c deleted file mode 100644 index d5730f3f..00000000 --- a/archive/rpg/entity/entityanim.c +++ /dev/null @@ -1,9 +0,0 @@ -/** - * Copyright (c) 2025 Dominic Masters - * - * This software is released under the MIT License. - * https://opensource.org/licenses/MIT - */ - -#include "entityanim.h" - diff --git a/archive/rpg/entity/entityanim.h b/archive/rpg/entity/entityanim.h deleted file mode 100644 index 42f7d40a..00000000 --- a/archive/rpg/entity/entityanim.h +++ /dev/null @@ -1,18 +0,0 @@ -/** - * Copyright (c) 2025 Dominic Masters - * - * This software is released under the MIT License. - * https://opensource.org/licenses/MIT - */ - -#pragma once -#include "dusk.h" - -#define ENTITY_ANIM_TURN_DURATION 0.06f -#define ENTITY_ANIM_WALK_DURATION 0.1f - -typedef enum { - ENTITY_ANIM_IDLE, - ENTITY_ANIM_TURN, - ENTITY_ANIM_WALK, -} entityanim_t; \ No newline at end of file diff --git a/archive/rpg/entity/entitydir.c b/archive/rpg/entity/entitydir.c deleted file mode 100644 index e272877a..00000000 --- a/archive/rpg/entity/entitydir.c +++ /dev/null @@ -1,51 +0,0 @@ -/** - * Copyright (c) 2025 Dominic Masters - * - * This software is released under the MIT License. - * https://opensource.org/licenses/MIT - */ - -#include "entitydir.h" -#include "assert/assert.h" - -entitydir_t entityDirGetOpposite(const entitydir_t dir) { - switch(dir) { - case ENTITY_DIR_NORTH: return ENTITY_DIR_SOUTH; - case ENTITY_DIR_SOUTH: return ENTITY_DIR_NORTH; - case ENTITY_DIR_EAST: return ENTITY_DIR_WEST; - case ENTITY_DIR_WEST: return ENTITY_DIR_EAST; - default: return dir; - } -} - -void entityDirGetRelative( - const entitydir_t from, - worldunits_t *outX, - worldunits_t *outY -) { - assertValidEntityDir(from, "Invalid direction provided"); - assertNotNull(outX, "Output X pointer cannot be NULL"); - assertNotNull(outY, "Output Y pointer cannot be NULL"); - - switch(from) { - case ENTITY_DIR_NORTH: - *outX = 0; - *outY = -1; - break; - - case ENTITY_DIR_EAST: - *outX = 1; - *outY = 0; - break; - - case ENTITY_DIR_SOUTH: - *outX = 0; - *outY = 1; - break; - - case ENTITY_DIR_WEST: - *outX = -1; - *outY = 0; - break; - } -} \ No newline at end of file diff --git a/archive/rpg/entity/entitydir.h b/archive/rpg/entity/entitydir.h deleted file mode 100644 index b132efb9..00000000 --- a/archive/rpg/entity/entitydir.h +++ /dev/null @@ -1,50 +0,0 @@ -/** - * Copyright (c) 2025 Dominic Masters - * - * This software is released under the MIT License. - * https://opensource.org/licenses/MIT - */ - -#pragma once -#include "rpg/overworld/worldpos.h" - -typedef enum { - ENTITY_DIR_UP = ENTITY_DIR_NORTH, - ENTITY_DIR_DOWN = ENTITY_DIR_SOUTH, - ENTITY_DIR_LEFT = ENTITY_DIR_WEST, - ENTITY_DIR_RIGHT = ENTITY_DIR_EAST, -} entitydir_t; - -/** - * Gets the opposite direction of a given direction. - * - * @param dir The direction to get the opposite of. - * @return entitydir_t The opposite direction. - */ -entitydir_t entityDirGetOpposite(const entitydir_t dir); - -/** - * Asserts a given direction is valid. - * - * @param dir The direction to validate. - * @param msg The message to display if the assertion fails. - */ -#define assertValidEntityDir(dir, msg) \ - assertTrue( \ - (dir) == ENTITY_DIR_NORTH || \ - (dir) == ENTITY_DIR_EAST || \ - (dir) == ENTITY_DIR_SOUTH || \ - (dir) == ENTITY_DIR_WEST, \ - msg \ - ) - -/** - * Gets the relative x and y offsets for a given direction. - * - * @param dir The direction to get offsets for. - * @param relX Pointer to store the relative x offset. - * @param relY Pointer to store the relative y offset. - */ -void entityDirGetRelative( - const entitydir_t dir, worldunits_t *relX, worldunits_t *relY -); \ No newline at end of file diff --git a/archive/rpg/entity/entitytype.h b/archive/rpg/entity/entitytype.h deleted file mode 100644 index 0e3e4baa..00000000 --- a/archive/rpg/entity/entitytype.h +++ /dev/null @@ -1,55 +0,0 @@ -/** - * 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/player.h" -#include "npc.h" - -typedef uint8_t entitytype_t; - -typedef union { - player_t player; - npc_t npc; -} entitytypedata_t; - -typedef struct { - /** - * Initialization callback for the entity type. - * @param entity Pointer to the entity to initialize. - */ - void (*init)(entity_t *entity); - - /** - * Movement callback for the entity type. - * @param entity Pointer to the entity to move. - */ - void (*movement)(entity_t *entity); - - /** - * Interaction callback for the entity type. - * @param player Pointer to the player entity. - * @param entity Pointer to the entity to interact with. - * @return True if the entity handled the interaction, false otherwise. - */ - bool_t (*interact)(entity_t *player, entity_t *entity); -} entitycallback_t; - -static const entitycallback_t ENTITY_CALLBACKS[ENTITY_TYPE_COUNT] = { - [ENTITY_TYPE_NULL] = { NULL }, - - [ENTITY_TYPE_PLAYER] = { - .init = playerInit, - .movement = playerInput - }, - - [ENTITY_TYPE_NPC] = { - .init = npcInit, - .movement = npcMovement, - .interact = npcInteract - } -}; \ No newline at end of file diff --git a/archive/rpg/entity/npc.c b/archive/rpg/entity/npc.c deleted file mode 100644 index ec6548bd..00000000 --- a/archive/rpg/entity/npc.c +++ /dev/null @@ -1,31 +0,0 @@ -/** - * Copyright (c) 2025 Dominic Masters - * - * This software is released under the MIT License. - * https://opensource.org/licenses/MIT - */ - -#include "entity.h" -#include "assert/assert.h" - -#include "rpg/cutscene/scene/testcutscene.h" -#include "rpg/rpgtextbox.h" - -void npcInit(entity_t *entity) { - assertNotNull(entity, "Entity pointer cannot be NULL"); -} - -void npcMovement(entity_t *entity) { - assertNotNull(entity, "Entity pointer cannot be NULL"); -} - -bool_t npcInteract(entity_t *player, entity_t *npc) { - assertNotNull(player, "Player entity pointer cannot be NULL"); - assertNotNull(npc, "NPC entity pointer cannot be NULL"); - - cutsceneSystemStartCutscene(&TEST_CUTSCENE); - - // rpgTextboxShow(RPG_TEXTBOX_POS_BOTTOM, "Hello World!"); - - return false; -}; \ No newline at end of file diff --git a/archive/rpg/entity/npc.h b/archive/rpg/entity/npc.h deleted file mode 100644 index df36f15e..00000000 --- a/archive/rpg/entity/npc.h +++ /dev/null @@ -1,37 +0,0 @@ -/** - * Copyright (c) 2025 Dominic Masters - * - * This software is released under the MIT License. - * https://opensource.org/licenses/MIT - */ - -#pragma once -#include "dusk.h" - -typedef struct entity_s entity_t; - -typedef struct { - void *nothing; -} npc_t; - -/** - * Initializes an NPC entity. - * - * @param entity Pointer to the entity structure to initialize. - */ -void npcInit(entity_t *entity); - -/** - * Updates an NPC entity. - * - * @param entity Pointer to the entity structure to update. - */ -void npcMovement(entity_t *entity); - -/** - * Handles interaction with an NPC entity. - * - * @param player Pointer to the player entity. - * @param npc Pointer to the NPC entity. - */ -bool_t npcInteract(entity_t *player, entity_t *npc); \ No newline at end of file diff --git a/archive/rpg/entity/player.c b/archive/rpg/entity/player.c deleted file mode 100644 index cf33d562..00000000 --- a/archive/rpg/entity/player.c +++ /dev/null @@ -1,54 +0,0 @@ -/** - * Copyright (c) 2025 Dominic Masters - * - * This software is released under the MIT License. - * https://opensource.org/licenses/MIT - */ - -#include "entity.h" -#include "assert/assert.h" -#include "rpg/rpgcamera.h" -#include "util/memory.h" -#include "time/time.h" - -void playerInit(entity_t *entity) { - assertNotNull(entity, "Entity pointer cannot be NULL"); -} - -void playerInput(entity_t *entity) { - assertNotNull(entity, "Entity pointer cannot be NULL"); - - // Turn - const playerinputdirmap_t *dirMap = PLAYER_INPUT_DIR_MAP; - do { - if(!inputIsDown(dirMap->action)) continue; - if(entity->direction == dirMap->direction) continue; - return entityTurn(entity, dirMap->direction); - } while((++dirMap)->action != 0xFF); - - // Walk - dirMap = PLAYER_INPUT_DIR_MAP; - do { - if(!inputIsDown(dirMap->action)) continue; - if(entity->direction != dirMap->direction) continue; - return entityWalk(entity, dirMap->direction); - } while((++dirMap)->action != 0xFF); - - // Interaction - if(inputPressed(INPUT_ACTION_ACCEPT)) { - worldunit_t x, y, z; - { - worldunits_t relX, relY; - entityDirGetRelative(entity->direction, &relX, &relY); - x = entity->position.x + relX; - y = entity->position.y + relY; - z = entity->position.z; - } - - entity_t *interact = entityGetAt((worldpos_t){ x, y, z }); - if(interact != NULL && ENTITY_CALLBACKS[interact->type].interact != NULL) { - if(ENTITY_CALLBACKS[interact->type].interact(entity, interact)) return; - } - } - -} \ No newline at end of file diff --git a/archive/rpg/entity/player.h b/archive/rpg/entity/player.h deleted file mode 100644 index 8c10f31c..00000000 --- a/archive/rpg/entity/player.h +++ /dev/null @@ -1,43 +0,0 @@ -/** - * Copyright (c) 2025 Dominic Masters - * - * This software is released under the MIT License. - * https://opensource.org/licenses/MIT - */ - -#pragma once -#include "input/input.h" - -typedef struct entity_s entity_t; - -typedef struct { - void *nothing; -} player_t; - -typedef struct { - inputaction_t action; - entitydir_t direction; -} playerinputdirmap_t; - -static const playerinputdirmap_t PLAYER_INPUT_DIR_MAP[] = { - { INPUT_ACTION_UP, ENTITY_DIR_NORTH }, - { INPUT_ACTION_DOWN, ENTITY_DIR_SOUTH }, - { INPUT_ACTION_LEFT, ENTITY_DIR_WEST }, - { INPUT_ACTION_RIGHT, ENTITY_DIR_EAST }, - - { 0xFF, 0xFF } -}; - -/** - * Initializes a player entity. - * - * @param entity Pointer to the entity structure to initialize. - */ -void playerInit(entity_t *entity); - -/** - * Handles movement logic for the player entity. - * - * @param entity Pointer to the player entity structure. - */ -void playerInput(entity_t *entity); \ No newline at end of file diff --git a/archive/rpg/overworld/CMakeLists.txt b/archive/rpg/overworld/CMakeLists.txt deleted file mode 100644 index b697bf8a..00000000 --- a/archive/rpg/overworld/CMakeLists.txt +++ /dev/null @@ -1,13 +0,0 @@ -# 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 - chunk.c - map.c - worldpos.c - tile.c -) \ No newline at end of file diff --git a/archive/rpg/overworld/chunk.c b/archive/rpg/overworld/chunk.c deleted file mode 100644 index 19a9999e..00000000 --- a/archive/rpg/overworld/chunk.c +++ /dev/null @@ -1,20 +0,0 @@ -/** - * Copyright (c) 2025 Dominic Masters - * - * This software is released under the MIT License. - * https://opensource.org/licenses/MIT - */ - -#include "chunk.h" - -uint32_t chunkGetTileIndex(const chunkpos_t position) { - return ( - (position.z * CHUNK_WIDTH * CHUNK_HEIGHT) + - (position.y * CHUNK_WIDTH) + - position.x - ); -} - -bool_t chunkPositionIsEqual(const chunkpos_t a, const chunkpos_t b) { - return (a.x == b.x) && (a.y == b.y) && (a.z == b.z); -} \ No newline at end of file diff --git a/archive/rpg/overworld/chunk.h b/archive/rpg/overworld/chunk.h deleted file mode 100644 index 508b15f9..00000000 --- a/archive/rpg/overworld/chunk.h +++ /dev/null @@ -1,38 +0,0 @@ -/** - * Copyright (c) 2025 Dominic Masters - * - * This software is released under the MIT License. - * https://opensource.org/licenses/MIT - */ - -#pragma once -#include "rpg/overworld/tile.h" -#include "worldpos.h" -#include "display/mesh/quad.h" - -typedef struct chunk_s { - chunkpos_t position; - tile_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]; -} chunk_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 chunkGetTileIndex(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 chunkPositionIsEqual(const chunkpos_t a, const chunkpos_t b); \ No newline at end of file diff --git a/archive/rpg/overworld/map.c b/archive/rpg/overworld/map.c deleted file mode 100644 index 32ff403a..00000000 --- a/archive/rpg/overworld/map.c +++ /dev/null @@ -1,269 +0,0 @@ -/** - * 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 "rpg/entity/entity.h" -#include "util/string.h" -#include "script/scriptcontext.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 - 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 - - // 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++) { - chunk_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++; - } - } - } - - // Execute map script. - char_t scriptPath[MAP_FILE_PATH_MAX + 16]; - stringFormat( - scriptPath, sizeof(scriptPath), "%s/%s.dsf", - MAP.dirPath, MAP.fileName - ); - if(assetFileExists(scriptPath)) { - scriptcontext_t ctx; - errorChain(scriptContextInit(&ctx)); - errorChain(scriptContextExecFile(&ctx, scriptPath)); - scriptContextDispose(&ctx); - } - - errorOk(); -} - -errorret_t mapPositionSet(const chunkpos_t newPos) { - if(!mapIsLoaded()) errorThrow("No map loaded"); - - const chunkpos_t curPos = MAP.chunkPosition; - if(chunkPositionIsEqual(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? - chunk_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++) { - chunk_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++) { - chunk_t *chunk = &MAP.chunks[chunksRemaining[i]]; - if(!chunkPositionIsEqual(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]; - chunk_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() { - -} - -void mapDispose() { - for(chunkindex_t i = 0; i < MAP_CHUNK_COUNT; i++) { - mapChunkUnload(&MAP.chunks[i]); - } -} - -void mapChunkUnload(chunk_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(chunk_t* chunk) { - if(!mapIsLoaded()) errorThrow("No map loaded"); - - char_t buffer[64]; - - // 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); -} - -chunk_t* mapGetChunk(const uint8_t index) { - if(index >= MAP_CHUNK_COUNT) return NULL; - if(!mapIsLoaded()) return NULL; - return MAP.chunkOrder[index]; -} - -tile_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; - - chunk_t *chunk = mapGetChunk(chunkIndex); - assertNotNull(chunk, "Chunk pointer cannot be NULL"); - chunktileindex_t tileIndex = worldPosToChunkTileIndex(&position); - return chunk->tiles[tileIndex]; -} \ No newline at end of file diff --git a/archive/rpg/overworld/map.h b/archive/rpg/overworld/map.h deleted file mode 100644 index 8373fca0..00000000 --- a/archive/rpg/overworld/map.h +++ /dev/null @@ -1,106 +0,0 @@ -/** - * Copyright (c) 2025 Dominic Masters - * - * This software is released under the MIT License. - * https://opensource.org/licenses/MIT - */ - -#pragma once -#include "rpg/overworld/chunk.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]; - - chunk_t chunks[MAP_CHUNK_COUNT]; - chunk_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(); - -/** - * 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(chunk_t* chunk); - -/** - * Loads a chunk. - * - * @param chunk The chunk to load. - * @return An error code. - */ -errorret_t mapChunkLoad(chunk_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. - */ -chunk_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. - */ -tile_t mapGetTile(const worldpos_t position); \ No newline at end of file diff --git a/archive/rpg/overworld/tile.c b/archive/rpg/overworld/tile.c deleted file mode 100644 index cc25881b..00000000 --- a/archive/rpg/overworld/tile.c +++ /dev/null @@ -1,35 +0,0 @@ -/** - * Copyright (c) 2025 Dominic Masters - * - * This software is released under the MIT License. - * https://opensource.org/licenses/MIT - */ - -#include "tile.h" - -bool_t tileIsWalkable(const tile_t tile) { - switch(tile) { - case TILE_SHAPE_NULL: - return false; - - default: - return true; - } -} - -bool_t tileIsRamp(const tile_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; - } -} \ No newline at end of file diff --git a/archive/rpg/overworld/tile.h b/archive/rpg/overworld/tile.h deleted file mode 100644 index 6b1d11bc..00000000 --- a/archive/rpg/overworld/tile.h +++ /dev/null @@ -1,27 +0,0 @@ -/** - * Copyright (c) 2025 Dominic Masters - * - * This software is released under the MIT License. - * https://opensource.org/licenses/MIT - */ - -#pragma once -#include "rpg/entity/entitydir.h" - -typedef uint8_t tile_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 tileIsWalkable(const tile_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 tileIsRamp(const tile_t tile); \ No newline at end of file diff --git a/archive/rpg/overworld/worldpos.c b/archive/rpg/overworld/worldpos.c deleted file mode 100644 index 3cdd7100..00000000 --- a/archive/rpg/overworld/worldpos.c +++ /dev/null @@ -1,97 +0,0 @@ -/** - * 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; -} \ No newline at end of file diff --git a/archive/rpg/overworld/worldpos.h b/archive/rpg/overworld/worldpos.h deleted file mode 100644 index a9432e4e..00000000 --- a/archive/rpg/overworld/worldpos.h +++ /dev/null @@ -1,75 +0,0 @@ -/** - * 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); \ No newline at end of file diff --git a/archive/rpg/rpg.c b/archive/rpg/rpg.c deleted file mode 100644 index 653b36fe..00000000 --- a/archive/rpg/rpg.c +++ /dev/null @@ -1,64 +0,0 @@ -/** - * Copyright (c) 2025 Dominic Masters - * - * This software is released under the MIT License. - * https://opensource.org/licenses/MIT - */ - -#include "rpg.h" -#include "entity/entity.h" -#include "rpg/overworld/map.h" -#include "rpg/cutscene/cutscenesystem.h" -#include "time/time.h" -#include "rpgcamera.h" -#include "rpgtextbox.h" -#include "util/memory.h" -#include "assert/assert.h" - -errorret_t rpgInit(void) { - memoryZero(ENTITIES, sizeof(ENTITIES)); - - // Init cutscene subsystem - cutsceneSystemInit(); - - errorChain(mapInit()); - - rpgCameraInit(); - rpgTextboxInit(); - - // TEST: Create some entities. - // uint8_t entIndex = entityGetAvailable(); - // assertTrue(entIndex != 0xFF, "No available entity slots!."); - // entity_t *ent = &ENTITIES[entIndex]; - // entityInit(ent, ENTITY_TYPE_PLAYER); - // RPG_CAMERA.mode = RPG_CAMERA_MODE_FOLLOW_ENTITY; - // RPG_CAMERA.followEntity.followEntityId = ent->id; - // ent->position.x = 2, ent->position.y = 2; - - // All Good! - errorOk(); -} - -errorret_t rpgUpdate(void) { - #if TIME_FIXED == 0 - if(TIME.dynamicUpdate) errorOk(); - #endif - - // TODO: Do not update if the scene is not the map scene? - mapUpdate(); - - // Update overworld ents. - entity_t *ent = &ENTITIES[0]; - do { - if(ent->type == ENTITY_TYPE_NULL) continue; - entityUpdate(ent); - } while(++ent < &ENTITIES[ENTITY_COUNT]); - - cutsceneSystemUpdate(); - errorChain(rpgCameraUpdate()); - errorOk(); -} - -void rpgDispose(void) { - mapDispose(); -} \ No newline at end of file diff --git a/archive/rpg/rpg.h b/archive/rpg/rpg.h deleted file mode 100644 index 7460dfb5..00000000 --- a/archive/rpg/rpg.h +++ /dev/null @@ -1,32 +0,0 @@ -/** - * Copyright (c) 2025 Dominic Masters - * - * This software is released under the MIT License. - * https://opensource.org/licenses/MIT - */ - -#pragma once -#include "error/error.h" - -typedef struct { - int32_t nothing; -} rpg_t; - -/** - * Initialize the RPG subsystem. - * - * @return An error code and state. - */ -errorret_t rpgInit(void); - -/** - * Update the RPG subsystem. - * - * @return An error code. - */ -errorret_t rpgUpdate(void); - -/** - * Dispose of the RPG subsystem. - */ -void rpgDispose(void); \ No newline at end of file diff --git a/archive/rpg/rpgcamera.c b/archive/rpg/rpgcamera.c deleted file mode 100644 index b32c0179..00000000 --- a/archive/rpg/rpgcamera.c +++ /dev/null @@ -1,52 +0,0 @@ -/** - * Copyright (c) 2025 Dominic Masters - * - * This software is released under the MIT License. - * https://opensource.org/licenses/MIT - */ - -#include "rpgcamera.h" -#include "util/memory.h" -#include "rpg/entity/entity.h" -#include "rpg/overworld/map.h" -#include "assert/assert.h" - -rpgcamera_t RPG_CAMERA; - -void rpgCameraInit(void) { - memoryZero(&RPG_CAMERA, sizeof(rpgcamera_t)); -} - -errorret_t rpgCameraUpdate(void) { - if(!mapIsLoaded()) errorOk(); - - chunkpos_t chunkPos; - - switch(RPG_CAMERA.mode) { - case RPG_CAMERA_MODE_FREE: - worldPosToChunkPos(&RPG_CAMERA.free, &chunkPos); - break; - - case RPG_CAMERA_MODE_FOLLOW_ENTITY: { - entity_t *entity = &ENTITIES[RPG_CAMERA.followEntity.followEntityId]; - if(entity->type == ENTITY_TYPE_NULL) { - errorOk(); - } - - // Update map position to match camera. By default map wants to know the - // top left but we want to set the center, so we need to sub half map size - worldPosToChunkPos(&entity->position, &chunkPos); - break; - } - - default: - assertUnreachable("Invalid RPG camera mode"); - } - - errorChain(mapPositionSet((chunkpos_t){ - .x = chunkPos.x - (MAP_CHUNK_WIDTH / 2), - .y = chunkPos.y - (MAP_CHUNK_HEIGHT / 2), - .z = chunkPos.z - (MAP_CHUNK_DEPTH / 2) - })); - errorOk(); -} \ No newline at end of file diff --git a/archive/rpg/rpgcamera.h b/archive/rpg/rpgcamera.h deleted file mode 100644 index d3e49663..00000000 --- a/archive/rpg/rpgcamera.h +++ /dev/null @@ -1,39 +0,0 @@ -/** - * Copyright (c) 2025 Dominic Masters - * - * This software is released under the MIT License. - * https://opensource.org/licenses/MIT - */ - -#pragma once -#include "rpg/overworld/worldpos.h" -#include "error/error.h" - -typedef enum { - RPG_CAMERA_MODE_FREE, - RPG_CAMERA_MODE_FOLLOW_ENTITY, -} rpgcameramode_t; - -typedef struct { - rpgcameramode_t mode; - union { - worldpos_t free; - struct { - uint8_t followEntityId; - } followEntity; - }; -} rpgcamera_t; - -extern rpgcamera_t RPG_CAMERA; - -/** - * Initializes the RPG camera. - */ -void rpgCameraInit(void); - -/** - * Updates the RPG camera. - * - * @return An error code. - */ -errorret_t rpgCameraUpdate(void); \ No newline at end of file diff --git a/archive/rpg/rpgtextbox.c b/archive/rpg/rpgtextbox.c deleted file mode 100644 index 6fba985c..00000000 --- a/archive/rpg/rpgtextbox.c +++ /dev/null @@ -1,39 +0,0 @@ -/** - * Copyright (c) 2025 Dominic Masters - * - * This software is released under the MIT License. - * https://opensource.org/licenses/MIT - */ - -#include "rpgtextbox.h" -#include "util/memory.h" -#include "util/string.h" -#include "assert/assert.h" - -rpgtextbox_t RPG_TEXTBOX; - -void rpgTextboxInit() { - memoryZero(&RPG_TEXTBOX, sizeof(rpgtextbox_t)); -} - -void rpgTextboxShow( - const rpgtextboxpos_t position, - const char_t *text -) { - RPG_TEXTBOX.position = position; - RPG_TEXTBOX.visible = true; - - stringCopy( - RPG_TEXTBOX.text, - text, - RPG_TEXTBOX_MAX_CHARS - ); -} - -void rpgTextboxHide() { - RPG_TEXTBOX.visible = false; -} - -bool_t rpgTextboxIsVisible() { - return RPG_TEXTBOX.visible; -} \ No newline at end of file diff --git a/archive/rpg/rpgtextbox.h b/archive/rpg/rpgtextbox.h deleted file mode 100644 index 02e8cd98..00000000 --- a/archive/rpg/rpgtextbox.h +++ /dev/null @@ -1,52 +0,0 @@ -/** - * Copyright (c) 2025 Dominic Masters - * - * This software is released under the MIT License. - * https://opensource.org/licenses/MIT - */ - -#pragma once -#include "dusk.h" - -#define RPG_TEXTBOX_MAX_CHARS 256 - -typedef enum { - RPG_TEXTBOX_POS_TOP, - RPG_TEXTBOX_POS_BOTTOM, -} rpgtextboxpos_t; - -typedef struct { - rpgtextboxpos_t position; - bool_t visible; - char_t text[RPG_TEXTBOX_MAX_CHARS]; -} rpgtextbox_t; - -extern rpgtextbox_t RPG_TEXTBOX; - -/** - * Initializes the RPG textbox. - */ -void rpgTextboxInit(); - -/** - * Shows the RPG textbox at a specified position. - * - * @param position The position to show the textbox at. - * @param text The text to display in the textbox (copied). - */ -void rpgTextboxShow( - const rpgtextboxpos_t position, - const char_t *text -); - -/** - * Hides the RPG textbox. - */ -void rpgTextboxHide(); - -/** - * Checks if the RPG textbox is currently visible. - * - * @return true if the textbox is visible, false otherwise. - */ -bool_t rpgTextboxIsVisible(); \ No newline at end of file diff --git a/archive/scene/CMakeLists.txt b/archive/scene/CMakeLists.txt deleted file mode 100644 index 94aaa08b..00000000 --- a/archive/scene/CMakeLists.txt +++ /dev/null @@ -1,13 +0,0 @@ -# Copyright (c) 2025 Dominic Masters -# -# This software is released under the MIT License. -# https://opensource.org/licenses/MIT - -# Sources -target_sources(${DUSK_LIBRARY_TARGET_NAME} - PUBLIC - scenemanager.c -) - -# Subdirs -add_subdirectory(scene) \ No newline at end of file diff --git a/archive/scene/scene.h b/archive/scene/scene.h deleted file mode 100644 index 39c89a4f..00000000 --- a/archive/scene/scene.h +++ /dev/null @@ -1,24 +0,0 @@ -/** - * Copyright (c) 2025 Dominic Masters - * - * This software is released under the MIT License. - * https://opensource.org/licenses/MIT - */ - -#pragma once -#include "dusk.h" -#include "error/error.h" -#include "display/color.h" - -#define SCENE_FLAG_INITIALIZED (1 << 0) - -typedef struct scenedata_s scenedata_t; - -typedef struct { - const char_t *name; - errorret_t (*init)(scenedata_t *data); - void (*update)(scenedata_t *data); - void (*render)(scenedata_t *data); - void (*dispose)(scenedata_t *data); - uint8_t flags; -} scene_t; \ No newline at end of file diff --git a/archive/scene/scene/CMakeLists.txt b/archive/scene/scene/CMakeLists.txt deleted file mode 100644 index e5e83ab0..00000000 --- a/archive/scene/scene/CMakeLists.txt +++ /dev/null @@ -1,13 +0,0 @@ -# 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 - scenetest.c - scenemap.c -) - -# Subdirs \ No newline at end of file diff --git a/archive/scene/scene/scenemap.c b/archive/scene/scene/scenemap.c deleted file mode 100644 index 76991160..00000000 --- a/archive/scene/scene/scenemap.c +++ /dev/null @@ -1,212 +0,0 @@ -/** - * Copyright (c) 2025 Dominic Masters - * - * This software is released under the MIT License. - * https://opensource.org/licenses/MIT - */ - -#include "scenemap.h" -#include "scene/scenedata.h" -#include "display/spritebatch.h" -#include "assert/assert.h" -#include "asset/asset.h" -#include "rpg/entity/entity.h" -#include "rpg/overworld/map.h" -#include "display/screen.h" -#include "rpg/rpgcamera.h" -#include "util/memory.h" -#include "duskdefs.h" - -errorret_t sceneMapInit(scenedata_t *data) { - // Init the camera. - cameraInitPerspective(&data->sceneMap.camera); - data->sceneMap.camera.projType = CAMERA_PROJECTION_TYPE_PERSPECTIVE_FLIPPED; - data->sceneMap.camera.viewType = CAMERA_VIEW_TYPE_LOOKAT_PIXEL_PERFECT; - glm_vec3_zero(data->sceneMap.camera.lookatPixelPerfect.offset); - data->sceneMap.camera.lookatPixelPerfect.offset[1] = RPG_CAMERA_Z_OFFSET; - glm_vec3_copy( - (vec3){ 0.0f, 0.0f, 0.0f }, - data->sceneMap.camera.lookatPixelPerfect.target - ); - glm_vec3_copy( - (vec3){ 0.0f, 1.0f, 0.0f }, - data->sceneMap.camera.lookatPixelPerfect.up - ); - data->sceneMap.camera.lookatPixelPerfect.pixelsPerUnit = ( - RPG_CAMERA_PIXELS_PER_UNIT - ); - data->sceneMap.camera.perspective.fov = glm_rad(RPG_CAMERA_FOV); - - errorOk(); -} - -void sceneMapUpdate(scenedata_t *data) { - -} - -void sceneMapGetWorldPosition(const worldpos_t pos, vec3 outPosition) { - assertNotNull(outPosition, "Output position cannot be NULL"); - - outPosition[0] = pos.x * TILE_WIDTH; - outPosition[1] = pos.y * TILE_HEIGHT; - outPosition[2] = pos.z * TILE_DEPTH; - - // Handle stair tiles. - tile_t tile = mapGetTile(pos); - if(tileIsRamp(tile)) { - outPosition[2] += TILE_DEPTH / 2.0f; - } -} - -void sceneMapEntityGetPosition(const entity_t *entity, vec3 outPosition) { - assertNotNull(entity, "Entity cannot be NULL"); - assertNotNull(outPosition, "Output position cannot be NULL"); - - // Get position - sceneMapGetWorldPosition(entity->position, outPosition); - - // Add a small offset so we render above the tile - outPosition[2] += 0.1f; - - // Add animation offset(s) - switch(entity->animation) { - case ENTITY_ANIM_WALK: { - float_t animPercentage = entity->animTime / ENTITY_ANIM_WALK_DURATION; - - vec3 lastPosition; - sceneMapGetWorldPosition(entity->lastPosition, lastPosition); - - vec3 offset; - glm_vec3_sub(outPosition, lastPosition, offset); - glm_vec3_scale(offset, -animPercentage, offset); - glm_vec3_add(outPosition, offset, outPosition); - break; - } - - default: - break; - } -} - -void sceneMapRender(scenedata_t *data) { - if(!mapIsLoaded()) return; - - // Look at target. - vec3 cameraTarget; - switch(RPG_CAMERA.mode) { - case RPG_CAMERA_MODE_FREE: - sceneMapGetWorldPosition(RPG_CAMERA.free, cameraTarget); - break; - - case RPG_CAMERA_MODE_FOLLOW_ENTITY: { - const entity_t *ent = &ENTITIES[RPG_CAMERA.followEntity.followEntityId]; - sceneMapEntityGetPosition(ent, cameraTarget); - break; - } - - default: - glm_vec3_zero(cameraTarget); - break; - } - - glm_vec3_copy( - cameraTarget, - data->sceneMap.camera.lookatPixelPerfect.target - ); - - // Push camera - cameraPushMatrix(&data->sceneMap.camera); - - // Render map probably. - sceneMapRenderMap(); - - // Render ents - entity_t *ent = ENTITIES; - do { - sceneMapRenderEntity(ent); - } while(++ent, ent < &ENTITIES[ENTITY_COUNT]); - spriteBatchFlush(); - - // Finished, pop back camera. - cameraPopMatrix(); -} - -void sceneMapRenderEntity(entity_t *entity) { - assertNotNull(entity, "Entity cannot be NULL"); - - if(entity->type == ENTITY_TYPE_NULL) return; - - vec3 posMin, posMax; - vec3 size = { TILE_WIDTH, TILE_HEIGHT, TILE_DEPTH }; - sceneMapEntityGetPosition(entity, posMin); - glm_vec3_add(posMin, size, posMax); - - // TEST: Change color depending on dir. - color_t testColor; - switch(entity->direction) { - case ENTITY_DIR_NORTH: - testColor = COLOR_BLUE; - break; - case ENTITY_DIR_EAST: - testColor = COLOR_GREEN; - break; - case ENTITY_DIR_SOUTH: - testColor = COLOR_CYAN; - break; - case ENTITY_DIR_WEST: - testColor = COLOR_YELLOW; - break; - default: - testColor = COLOR_WHITE; - break; - } - - vec2 uv0 = { 0.0f, 0.0f }; - vec2 uv1 = { 1.0f, 1.0f }; - - spriteBatchPush3D(NULL, posMin, posMax, testColor, uv0, uv1); -} - -void sceneMapRenderMap() { - assertTrue(mapIsLoaded(), "No map loaded to render"); - - // For each chunk. - for(uint32_t i = 0; i < MAP_CHUNK_COUNT; i++) { - chunk_t *chunk = MAP.chunkOrder[i]; - - for(uint8_t j = 0; j < chunk->meshCount; j++) { - mesh_t *mesh = &chunk->meshes[j]; - if(mesh->vertexCount == 0) continue; - textureBind(NULL); - meshDraw(mesh, -1, -1); - } - - // vec3 min, max; - // min[0] = chunk->position.x * CHUNK_WIDTH * TILE_WIDTH; - // min[1] = chunk->position.y * CHUNK_HEIGHT * TILE_HEIGHT; - // min[2] = chunk->position.z * CHUNK_DEPTH * TILE_DEPTH; - - // max[0] = min[0] + (CHUNK_WIDTH * TILE_WIDTH); - // max[1] = min[1] + (CHUNK_HEIGHT * TILE_HEIGHT); - // max[2] = min[2]; - - // color_t color = COLOR_WHITE; - // if(chunk->position.x % 2 == 0) { - // color = (chunk->position.y % 2 == 0) ? COLOR_BLACK : COLOR_WHITE; - // } else { - // color = (chunk->position.y % 2 == 0) ? COLOR_WHITE : COLOR_BLACK; - // } - - // spriteBatchPush3D( - // NULL, - // min, - // max, - // color, - // (vec2){ 0.0f, 0.0f }, - // (vec2){ 1.0f, 1.0f } - // ); - } -} - -void sceneMapDispose(scenedata_t *data) { -} diff --git a/archive/scene/scene/scenemap.h b/archive/scene/scene/scenemap.h deleted file mode 100644 index 8e28b7c4..00000000 --- a/archive/scene/scene/scenemap.h +++ /dev/null @@ -1,32 +0,0 @@ -/** - * Copyright (c) 2025 Dominic Masters - * - * This software is released under the MIT License. - * https://opensource.org/licenses/MIT - */ - -#pragma once -#include "scene/scene.h" -#include "rpg/entity/entity.h" -#include "display/camera/camera.h" -#include "asset/asset.h" - -typedef struct { - camera_t camera; -} scenemap_t; - -errorret_t sceneMapInit(scenedata_t *data); -void sceneMapUpdate(scenedata_t *data); -void sceneMapRender(scenedata_t *data); -void sceneMapRenderEntity(entity_t *entity); -void sceneMapRenderMap(); -void sceneMapDispose(scenedata_t *data); - -static scene_t SCENE_MAP = { - .name = "map", - .init = sceneMapInit, - .update = sceneMapUpdate, - .render = sceneMapRender, - .dispose = sceneMapDispose, - .flags = 0 -}; \ No newline at end of file diff --git a/archive/scene/scene/scenetest.c b/archive/scene/scene/scenetest.c deleted file mode 100644 index 1fba8990..00000000 --- a/archive/scene/scene/scenetest.c +++ /dev/null @@ -1,26 +0,0 @@ -/** - * Copyright (c) 2025 Dominic Masters - * - * This software is released under the MIT License. - * https://opensource.org/licenses/MIT - */ - -#include "scenetest.h" -#include "scene/scenedata.h" - -errorret_t sceneTestInit(scenedata_t *data) { - data->sceneTest.nothing = 0; - errorOk(); -} - -void sceneTestUpdate(scenedata_t *data) { - -} - -void sceneTestRender(scenedata_t *data) { - -} - -void sceneTestDispose(scenedata_t *data) { - -} \ No newline at end of file diff --git a/archive/scene/scene/scenetest.h b/archive/scene/scene/scenetest.h deleted file mode 100644 index de8051d0..00000000 --- a/archive/scene/scene/scenetest.h +++ /dev/null @@ -1,27 +0,0 @@ -/** - * Copyright (c) 2025 Dominic Masters - * - * This software is released under the MIT License. - * https://opensource.org/licenses/MIT - */ - -#pragma once -#include "scene/scene.h" - -typedef struct { - int32_t nothing; -} scenetest_t; - -errorret_t sceneTestInit(scenedata_t *data); -void sceneTestUpdate(scenedata_t *data); -void sceneTestRender(scenedata_t *data); -void sceneTestDispose(scenedata_t *data); - -static scene_t SCENE_TEST = { - .name = "test", - .init = sceneTestInit, - .update = sceneTestUpdate, - .render = sceneTestRender, - .dispose = sceneTestDispose, - .flags = 0 -}; \ No newline at end of file diff --git a/archive/scene/scenedata.h b/archive/scene/scenedata.h deleted file mode 100644 index 948bed61..00000000 --- a/archive/scene/scenedata.h +++ /dev/null @@ -1,12 +0,0 @@ -/** - * Copyright (c) 2025 Dominic Masters - * - * This software is released under the MIT License. - * https://opensource.org/licenses/MIT - */ - -#pragma once -#include "scene/scene.h" - -#include "scene/scene/scenetest.h" -#include "scene/scene/scenemap.h" \ No newline at end of file diff --git a/archive/scene/scenemanager.c b/archive/scene/scenemanager.c deleted file mode 100644 index aff10b3c..00000000 --- a/archive/scene/scenemanager.c +++ /dev/null @@ -1,119 +0,0 @@ -/** - * Copyright (c) 2025 Dominic Masters - * - * This software is released under the MIT License. - * https://opensource.org/licenses/MIT - */ - -#include "scenemanager.h" -#include "util/memory.h" -#include "assert/assert.h" -#include "display/framebuffer.h" -#include "util/string.h" -#include "asset/asset.h" -#include "script/scriptmanager.h" - -scenemanager_t SCENE_MANAGER; - -errorret_t sceneManagerInit(void) { - memoryZero(&SCENE_MANAGER, sizeof(scenemanager_t)); - - sceneManagerRegisterScene(&SCENE_TEST); - sceneManagerRegisterScene(&SCENE_MAP); - - errorOk(); -} - -scene_t * sceneManagerGetSceneByName(const char_t *name) { - assertNotNull(name, "Name is null"); - - for(uint8_t i = 0; i < SCENE_MANAGER.sceneCount; i++) { - if(strcmp(SCENE_MANAGER.scenes[i]->name, name) != 0) continue; - return SCENE_MANAGER.scenes[i]; - } - - return NULL; -} - -void sceneManagerRegisterScene(scene_t *scene) { - assertNotNull(scene, "Scene is null"); - assertTrue( - SCENE_MANAGER.sceneCount < SCENE_MANAGER_SCENE_COUNT_MAX, - "Scene count exceeded max" - ); - assertNotNull(scene->name, "Scene name is null"); - assertNull( - sceneManagerGetSceneByName(scene->name), "Scene name already registered" - ); - - SCENE_MANAGER.scenes[SCENE_MANAGER.sceneCount++] = scene; -} - -errorret_t sceneManagerSetScene(scene_t *scene) { - if( - SCENE_MANAGER.current && - (SCENE_MANAGER.current->flags & SCENE_FLAG_INITIALIZED) != 0 - ) { - SCENE_MANAGER.current->flags &= ~SCENE_FLAG_INITIALIZED; - if(SCENE_MANAGER.current->dispose) { - SCENE_MANAGER.current->dispose(&SCENE_MANAGER.sceneData); - } - } - - SCENE_MANAGER.current = scene; - - if(scene && (scene->flags & SCENE_FLAG_INITIALIZED) == 0) { - scene->flags |= SCENE_FLAG_INITIALIZED; - - if(scene->init) errorChain(scene->init(&SCENE_MANAGER.sceneData)); - - // Execute scene script if it exists - char_t buffer[256]; - snprintf(buffer, sizeof(buffer), "scene/%s.dsf", scene->name); - if(assetFileExists(buffer)) { - scriptcontext_t ctx; - scriptContextInit(&ctx); - errorChain(scriptContextExecFile(&ctx, buffer)); - scriptContextDispose(&ctx); - } - } - - errorOk(); -} - -void sceneManagerUpdate(void) { - if(!SCENE_MANAGER.current) return; - - assertTrue( - SCENE_MANAGER.current->flags & SCENE_FLAG_INITIALIZED, - "Current scene not initialized" - ); - - if(SCENE_MANAGER.current->update) { - SCENE_MANAGER.current->update(&SCENE_MANAGER.sceneData); - } -} - -void sceneManagerRender(void) { - if(!SCENE_MANAGER.current) return; - - assertTrue( - SCENE_MANAGER.current->flags & SCENE_FLAG_INITIALIZED, - "Current scene not initialized" - ); - - if(SCENE_MANAGER.current->render) { - SCENE_MANAGER.current->render(&SCENE_MANAGER.sceneData); - } -} - -void sceneManagerDispose(void) { - for(uint8_t i = 0; i < SCENE_MANAGER.sceneCount; i++) { - scene_t *scene = SCENE_MANAGER.scenes[i]; - if(scene->flags & SCENE_FLAG_INITIALIZED) { - scene->dispose(&SCENE_MANAGER.sceneData); - } - } - - SCENE_MANAGER.sceneCount = 0; -} diff --git a/archive/scene/scenemanager.h b/archive/scene/scenemanager.h deleted file mode 100644 index d31a96ff..00000000 --- a/archive/scene/scenemanager.h +++ /dev/null @@ -1,63 +0,0 @@ -/** - * Copyright (c) 2025 Dominic Masters - * - * This software is released under the MIT License. - * https://opensource.org/licenses/MIT - */ - -#pragma once -#include "scene.h" -#include "scenedata.h" - -#define SCENE_MANAGER_SCENE_COUNT_MAX 16 - -typedef struct { - scene_t *current; - scene_t *scenes[SCENE_MANAGER_SCENE_COUNT_MAX]; - uint8_t sceneCount; -} scenemanager_t; - -extern scenemanager_t SCENE_MANAGER; - -/** - * Initializes the scene manager and the initial scene. - */ -errorret_t sceneManagerInit(void); - -/** - * Retrieves a registered scene by its name. - * - * @param name The name of the scene to retrieve. - * @return The scene with the specified name, or NULL if not found. - */ -scene_t * sceneManagerGetSceneByName(const char_t *name); - -/** - * Registers a scene with the scene manager. - * - * @param scene The scene to register. - */ -void sceneManagerRegisterScene(scene_t *scene); - -/** - * Sets the current active scene. - * - * @param scene The scene to set as current. - * @return An error code indicating success or failure. - */ -errorret_t sceneManagerSetScene(scene_t *scene); - -/** - * Updates all active scenes. - */ -void sceneManagerUpdate(void); - -/** - * Renders all visible scenes. - */ -void sceneManagerRender(void); - -/** - * Disposes of all scenes. - */ -void sceneManagerDispose(void); \ No newline at end of file diff --git a/archive/scriptfuncentity.h b/archive/scriptfuncentity.h deleted file mode 100644 index 40d44227..00000000 --- a/archive/scriptfuncentity.h +++ /dev/null @@ -1,134 +0,0 @@ -/** - * Copyright (c) 2025 Dominic Masters - * - * This software is released under the MIT License. - * https://opensource.org/licenses/MIT - */ - -#pragma once -#include "script/scriptcontext.h" -#include "rpg/entity/entity.h" -#include "assert/assert.h" - -int scriptFuncEntityAdd(lua_State *L) { - assertNotNull(L, "Lua state cannot be NULL"); - assertTrue(lua_isnumber(L, 1), "Expected integer entity type"); - - entitytype_t entityType = (entitytype_t)luaL_checknumber(L, 1); - assertTrue( - entityType >= ENTITY_TYPE_NULL && entityType < ENTITY_TYPE_COUNT, - "Invalid entity type passed to scriptFuncEntityAdd" - ); - - // Pop entity - uint8_t available = entityGetAvailable(); - if(available == 0xFF) { - lua_pushnil(L); - return 1; - } - - entity_t *ent = &ENTITIES[available]; - entityInit(ent, (entitytype_t)entityType); - - // May include X, Y and/or Z - if(lua_isinteger(L, 2)) { - lua_Integer xPos = luaL_checkinteger(L, 2); - ent->position.x = (int32_t)xPos; - } - if(lua_isinteger(L, 3)) { - lua_Integer yPos = luaL_checkinteger(L, 3); - ent->position.y = (int32_t)yPos; - } - if(lua_isinteger(L, 4)) { - lua_Integer zPos = luaL_checkinteger(L, 4); - ent->position.z = (int32_t)zPos; - } - - // Send entity id. - lua_pushinteger(L, ent->id); - return 1; -} - -int scriptFuncEntitySetX(lua_State *L) { - assertNotNull(L, "Lua state cannot be NULL"); - - assertTrue(lua_isinteger(L, 1), "Expected integer entity id"); - assertTrue(lua_isinteger(L, 2), "Expected integer x position"); - - lua_Integer entityId = luaL_checkinteger(L, 1); - lua_Integer xPos = luaL_checkinteger(L, 2); - - assertTrue( - entityId >= 0 && entityId < ENTITY_COUNT, - "Invalid entity id passed to scriptFuncEntitySetX" - ); - - entity_t *ent = &ENTITIES[entityId]; - assertTrue( - ent->type != ENTITY_TYPE_NULL, - "Cannot set position of NULL entity in scriptFuncEntitySetX" - ); - - ent->position.x = (int32_t)xPos; - - return 0; -} - -int scriptFuncEntitySetY(lua_State *L) { - assertNotNull(L, "Lua state cannot be NULL"); - - assertTrue(lua_isinteger(L, 1), "Expected integer entity id"); - assertTrue(lua_isinteger(L, 2), "Expected integer y position"); - - lua_Integer entityId = luaL_checkinteger(L, 1); - lua_Integer yPos = luaL_checkinteger(L, 2); - - assertTrue( - entityId >= 0 && entityId < ENTITY_COUNT, - "Invalid entity id passed to scriptFuncEntitySetY" - ); - - entity_t *ent = &ENTITIES[entityId]; - assertTrue( - ent->type != ENTITY_TYPE_NULL, - "Cannot set position of NULL entity in scriptFuncEntitySetY" - ); - - ent->position.y = (int32_t)yPos; - - return 0; -} - -int scriptFuncEntitySetZ(lua_State *L) { - assertNotNull(L, "Lua state cannot be NULL"); - - assertTrue(lua_isinteger(L, 1), "Expected integer entity id"); - assertTrue(lua_isinteger(L, 2), "Expected integer z position"); - - lua_Integer entityId = luaL_checkinteger(L, 1); - lua_Integer zPos = luaL_checkinteger(L, 2); - - assertTrue( - entityId >= 0 && entityId < ENTITY_COUNT, - "Invalid entity id passed to scriptFuncEntitySetZ" - ); - - entity_t *ent = &ENTITIES[entityId]; - assertTrue( - ent->type != ENTITY_TYPE_NULL, - "Cannot set position of NULL entity in scriptFuncEntitySetZ" - ); - - ent->position.z = (int32_t)zPos; - - return 0; -} - -void scriptFuncEntity(scriptcontext_t *context) { - assertNotNull(context, "Script context cannot be NULL"); - - lua_register(context->luaState, "entityAdd", scriptFuncEntityAdd); - lua_register(context->luaState, "entitySetX", scriptFuncEntitySetX); - lua_register(context->luaState, "entitySetY", scriptFuncEntitySetY); - lua_register(context->luaState, "entitySetZ", scriptFuncEntitySetZ); -} \ No newline at end of file diff --git a/archive/ui/CMakeLists.txt b/archive/ui/CMakeLists.txt deleted file mode 100644 index 0155c3b3..00000000 --- a/archive/ui/CMakeLists.txt +++ /dev/null @@ -1,17 +0,0 @@ -# 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 - ui.c - uitext.c - uidebug.c - uiframe.c - uitextbox.c -) - -# Subdirs -add_subdirectory(element) \ No newline at end of file diff --git a/archive/ui/element/CMakeLists.txt b/archive/ui/element/CMakeLists.txt deleted file mode 100644 index 0b31e39a..00000000 --- a/archive/ui/element/CMakeLists.txt +++ /dev/null @@ -1,9 +0,0 @@ -# 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 -) \ No newline at end of file diff --git a/archive/ui/element/uielementtype.h b/archive/ui/element/uielementtype.h deleted file mode 100644 index 32c2367a..00000000 --- a/archive/ui/element/uielementtype.h +++ /dev/null @@ -1,16 +0,0 @@ -/** - * Copyright (c) 2025 Dominic Masters - * - * This software is released under the MIT License. - * https://opensource.org/licenses/MIT - */ - -#pragma once - -typedef enum { - UI_ELEMENT_TYPE_NULL, - - UI_ELEMENT_TYPE_TEXT, - - UI_ELEMENT_TYPE_COUNT, -} uielementtype_t; \ No newline at end of file diff --git a/archive/ui/ui.c b/archive/ui/ui.c deleted file mode 100644 index c6bf8834..00000000 --- a/archive/ui/ui.c +++ /dev/null @@ -1,67 +0,0 @@ -/** - * Copyright (c) 2025 Dominic Masters - * - * This software is released under the MIT License. - * https://opensource.org/licenses/MIT - */ - -#include "ui.h" -#include "assert/assert.h" -#include "ui/uidebug.h" -#include "util/memory.h" -#include "display/tileset/tileset_minogram.h" -#include "display/screen.h" -// #include "ui/uitextbox.h" - -ui_t UI; - -errorret_t uiInit(void) { - memoryZero(&UI, sizeof(ui_t)); - - cameraInitOrthographic(&UI.camera); - - // Initialize UI components here - uiSetFont(&TILESET_MINOGRAM); - - errorOk(); -} - -void uiUpdate(void) { - // Update UI state here - UI.camera.orthographic.left = 0; - UI.camera.orthographic.right = SCREEN.width; - UI.camera.orthographic.top = 0; - UI.camera.orthographic.bottom = SCREEN.height; - - // uiTextboxUpdate(); -} - -void uiRender(void) { - cameraPushMatrix(&UI.camera); - - // Render UI elements here - if(UI.fontTexture.width > 0) { - uiDebugRender(UI.fontTileset, &UI.fontTexture); - } - cameraPopMatrix(); -} - -errorret_t uiSetFont(const tileset_t *fontTileset) { - if(UI.fontTexture.width > 0) { - textureDispose(&UI.fontTexture); - UI.fontTexture.width = -1; - } - - assertNotNull(fontTileset, "Font tileset cannot be NULL."); - - UI.fontTileset = fontTileset; - errorChain(assetLoad(UI.fontTileset->image, &UI.fontTexture)); - errorOk(); -} - -void uiDispose(void) { - if(UI.fontTexture.width > 0) { - textureDispose(&UI.fontTexture); - UI.fontTexture.width = -1; - } -} \ No newline at end of file diff --git a/archive/ui/ui.h b/archive/ui/ui.h deleted file mode 100644 index 956fe450..00000000 --- a/archive/ui/ui.h +++ /dev/null @@ -1,51 +0,0 @@ -/** - * Copyright (c) 2025 Dominic Masters - * - * This software is released under the MIT License. - * https://opensource.org/licenses/MIT - */ - -#pragma once -#include "asset/asset.h" -#include "display/texture.h" -#include "display/tileset/tileset.h" -#include "display/camera/camera.h" - -typedef struct { - camera_t camera; - texture_t fontTexture; - const tileset_t *fontTileset; -} ui_t; - -extern ui_t UI; - -/** - * Initializes the UI system, loading necessary resources. - * - * @return An errorret_t indicating success or failure. - */ -errorret_t uiInit(void); - -/** - * Updates the UI state, handling user interactions and animations. - */ -void uiUpdate(void); - -/** - * Renders the UI elements to the screen. - */ -void uiRender(void); - -/** - * Sets the font tileset for UI text rendering. - * - * @param fontTileset Pointer to the tileset to use for UI fonts. - * - * @return An errorret_t indicating success or failure. - */ -errorret_t uiSetFont(const tileset_t *fontTileset); - -/** - * Cleans up and frees all UI resources. - */ -void uiDispose(void); \ No newline at end of file diff --git a/archive/ui/uidebug.c b/archive/ui/uidebug.c deleted file mode 100644 index 56e7db54..00000000 --- a/archive/ui/uidebug.c +++ /dev/null @@ -1,98 +0,0 @@ -/** - * Copyright (c) 2025 Dominic Masters - * - * This software is released under the MIT License. - * https://opensource.org/licenses/MIT - */ - -#include "uidebug.h" -#include "time/time.h" -#include "util/string.h" -#include "ui/uitext.h" -#include "display/screen.h" -#include "display/spritebatch.h" -// #include "rpg/entity/entity.h" - -bool_t UI_DEBUG_DRAW = true; - -void uiDebugRender(const tileset_t *tileset, texture_t *texture) { - if(!UI_DEBUG_DRAW) return; - - char_t buffer[128]; - color_t color; - int32_t w, h, hOffset = 0; - - // FPS Meter - #if TIME_FIXED == 0 - float_t fpsDynamic = TIME.dynamicDelta > 0.0f ? (1.0f / TIME.dynamicDelta) : 0.0f; - float_t fpsFixed = TIME.delta > 0.0f ? (1.0f / TIME.delta) : 0.0f; - snprintf( - buffer, - sizeof(buffer), - "%.2f/%.2f/%d/%d", - TIME.dynamicDelta * 1000.0f, - TIME.delta * 1000.0f, - TIME.dynamicUpdate, - (int32_t)fpsDynamic - ); - - color = ( - fpsDynamic >= 50.0f ? COLOR_GREEN : - fpsDynamic >= 30.0f ? COLOR_YELLOW : - COLOR_RED - ); - #else - float_t fps = TIME.delta > 0.0f ? (1.0f / TIME.delta) : 0.0f; - snprintf( - buffer, - sizeof(buffer), - "%.2f/%d/FXD", - TIME.delta * 1000.0f, - (int32_t)fps - ); - - color = ( - fps >= 50.0f ? COLOR_GREEN : - fps >= 30.0f ? COLOR_YELLOW : - COLOR_RED - ); - #endif - - uiTextMeasure(buffer, tileset, &w, &h); - uiTextDraw( - SCREEN.width - w, hOffset, - buffer, color, tileset, texture - ); - hOffset += h; - - - // Player position - // entity_t *player = NULL; - // for(uint8_t i = 0; i < ENTITY_COUNT; i++) { - // if(ENTITIES[i].type != ENTITY_TYPE_PLAYER) continue; - // player = &ENTITIES[i]; - // break; - // } - // if(player == NULL) { - // snprintf(buffer, sizeof(buffer), "Player: N/A"); - // } else { - // snprintf( - // buffer, - // sizeof(buffer), - // "%d,%d,%d/%d/%d", - // player->position.x, - // player->position.y, - // player->position.z, - // (int32_t)player->direction, - // (int32_t)player->animation - // ); - // } - // uiTextMeasure(buffer, tileset, &w, &h); - // uiTextDraw( - // SCREEN.width - w, hOffset, - // buffer, COLOR_GREEN, tileset, texture - // ); - // hOffset += h; - - spriteBatchFlush(); -} \ No newline at end of file diff --git a/archive/ui/uidebug.h b/archive/ui/uidebug.h deleted file mode 100644 index 3f106dfe..00000000 --- a/archive/ui/uidebug.h +++ /dev/null @@ -1,18 +0,0 @@ -/** - * Copyright (c) 2025 Dominic Masters - * - * This software is released under the MIT License. - * https://opensource.org/licenses/MIT - */ - -#pragma once -#include "display/tileset/tileset.h" -#include "display/texture.h" - -/** - * Renders the debug information UI element. - * - * @param tileset The tileset to use for rendering text. - * @param texture The texture associated with the tileset. - */ -void uiDebugRender(const tileset_t *tileset, texture_t *texture); \ No newline at end of file diff --git a/archive/ui/uiframe.c b/archive/ui/uiframe.c deleted file mode 100644 index f3e32c65..00000000 --- a/archive/ui/uiframe.c +++ /dev/null @@ -1,390 +0,0 @@ -/** - * Copyright (c) 2025 Dominic Masters - * - * This software is released under the MIT License. - * https://opensource.org/licenses/MIT - */ - -#include "uiframe.h" -#include "display/spritebatch.h" -#include "assert/assert.h" -#include "util/math.h" - -void uiFrameDraw( - const float_t x, - const float_t y, - const float_t width, - const float_t height, - const tileset_t *tileset, - const uint16_t column, - const uint16_t row, - const bool_t drawInner, - texture_t *texture -) { - assertNotNull(tileset, "Tileset cannot be NULL"); - assertNotNull(texture, "Texture cannot be NULL"); - - if(height <= 0 || width <= 0) return; - - // Top-Left - vec4 uv; - - tilesetPositionGetUV( - tileset, - column, - row, - uv - ); - spriteBatchPush( - texture, - x, y, - x + tileset->tileWidth, - y + tileset->tileHeight, - COLOR_WHITE, - uv[0], uv[1], uv[2], uv[3] - ); - - // Top-Center - tilesetPositionGetUV( - tileset, - column + 1, - row, - uv - ); - spriteBatchPush( - texture, - x + tileset->tileWidth, y, - x + width - tileset->tileWidth, y + tileset->tileHeight, - COLOR_WHITE, - uv[0], uv[1], uv[2], uv[3] - ); - - // Top-Right - tilesetPositionGetUV( - tileset, - column + 2, - row, - uv - ); - spriteBatchPush( - texture, - x + width - tileset->tileWidth, y, - x + width, y + tileset->tileHeight, - COLOR_WHITE, - uv[0], uv[1], uv[2], uv[3] - ); - - // Middle-Left - tilesetPositionGetUV( - tileset, - column, - row + 1, - uv - ); - spriteBatchPush( - texture, - x, y + tileset->tileHeight, - x + tileset->tileWidth, y + height - tileset->tileHeight, - COLOR_WHITE, - uv[0], uv[1], uv[2], uv[3] - ); - - // Middle-Center - if(drawInner) { - tilesetPositionGetUV( - tileset, - column + 1, - row + 1, - uv - ); - spriteBatchPush( - texture, - x + tileset->tileWidth, y + tileset->tileHeight, - x + width - tileset->tileWidth, y + height - tileset->tileHeight, - COLOR_WHITE, - uv[0], uv[1], uv[2], uv[3] - ); - } - - // Middle-Right - tilesetPositionGetUV( - tileset, - column + 2, - row + 1, - uv - ); - spriteBatchPush( - texture, - x + width - tileset->tileWidth, y + tileset->tileHeight, - x + width, y + height - tileset->tileHeight, - COLOR_WHITE, - uv[0], uv[1], uv[2], uv[3] - ); - - // Bottom-Left - tilesetPositionGetUV( - tileset, - column, - row + 2, - uv - ); - spriteBatchPush( - texture, - x, y + height - tileset->tileHeight, - x + tileset->tileWidth, y + height, - COLOR_WHITE, - uv[0], uv[1], uv[2], uv[3] - ); - - // Bottom-Center - tilesetPositionGetUV( - tileset, - column + 1, - row + 2, - uv - ); - spriteBatchPush( - texture, - x + tileset->tileWidth, y + height - tileset->tileHeight, - x + width - tileset->tileWidth, y + height, - COLOR_WHITE, - uv[0], uv[1], uv[2], uv[3] - ); - - // Bottom-Right - tilesetPositionGetUV( - tileset, - column + 2, - row + 2, - uv - ); - spriteBatchPush( - texture, - x + width - tileset->tileWidth, y + height - tileset->tileHeight, - x + width, y + height, - COLOR_WHITE, - uv[0], uv[1], uv[2], uv[3] - ); -} - -void uiFrameDrawTiled( - const float_t x, - const float_t y, - const float_t width, - const float_t height, - const tileset_t *tileset, - const uint16_t column, - const uint16_t row, - const bool_t drawInner, - texture_t *texture -) { - assertNotNull(tileset, "Tileset cannot be NULL"); - assertNotNull(texture, "Texture cannot be NULL"); - - if(height <= 0 || width <= 0) return; - - uint32_t segmentsX, segmentsY; - float_t remainderX, remainderY; - segmentsX = (width - (tileset->tileWidth * 2)) / tileset->tileWidth; - segmentsY = (height - (tileset->tileHeight * 2)) / tileset->tileHeight; - remainderX = fmodf(width - (tileset->tileWidth * 2), tileset->tileWidth); - remainderY = fmodf(height - (tileset->tileHeight * 2), tileset->tileHeight); - - // Corners - vec4 uv; - - // Top-Left - tilesetPositionGetUV( - tileset, - column, - row, - uv - ); - spriteBatchPush( - texture, - x, y, - x + tileset->tileWidth, - y + tileset->tileHeight, - COLOR_WHITE, - uv[0], uv[1], uv[2], uv[3] - ); - - // Top-Right - tilesetPositionGetUV( - tileset, - column + 2, - row, - uv - ); - spriteBatchPush( - texture, - x + width - tileset->tileWidth, y, - x + width, y + tileset->tileHeight, - COLOR_WHITE, - uv[0], uv[1], uv[2], uv[3] - ); - - // Bottom-Left - tilesetPositionGetUV( - tileset, - column, - row + 2, - uv - ); - spriteBatchPush( - texture, - x, y + height - tileset->tileHeight, - x + tileset->tileWidth, y + height, - COLOR_WHITE, - uv[0], uv[1], uv[2], uv[3] - ); - - // Bottom-Right - tilesetPositionGetUV( - tileset, - column + 2, - row + 2, - uv - ); - spriteBatchPush( - texture, - x + width - tileset->tileWidth, y + height - tileset->tileHeight, - x + width, y + height, - COLOR_WHITE, - uv[0], uv[1], uv[2], uv[3] - ); - - // Top and Bottom Edges (Tiled) - tilesetPositionGetUV(tileset, column + 1, row, uv); // Top edge - float_t edgeX = x + tileset->tileWidth; - for(uint32_t i = 0; i < segmentsX; ++i, edgeX += tileset->tileWidth) { - spriteBatchPush( - texture, - edgeX, y, - edgeX + tileset->tileWidth, y + tileset->tileHeight, - COLOR_WHITE, - uv[0], uv[1], uv[2], uv[3] - ); - } - if(remainderX) { - spriteBatchPush( - texture, - edgeX, y, - edgeX + remainderX, y + tileset->tileHeight, - COLOR_WHITE, - uv[0], uv[1], uv[2], uv[3] - ); - } - - tilesetPositionGetUV(tileset, column + 1, row + 2, uv); // Bottom edge - edgeX = x + tileset->tileWidth; - for(uint32_t i = 0; i < segmentsX; ++i, edgeX += tileset->tileWidth) { - spriteBatchPush( - texture, - edgeX, y + height - tileset->tileHeight, - edgeX + tileset->tileWidth, y + height, - COLOR_WHITE, - uv[0], uv[1], uv[2], uv[3] - ); - } - if(remainderX) { - spriteBatchPush( - texture, - edgeX, y + height - tileset->tileHeight, - edgeX + remainderX, y + height, - COLOR_WHITE, - uv[0], uv[1], uv[2], uv[3] - ); - } - - // Left and Right Edges (Tiled) - tilesetPositionGetUV(tileset, column, row + 1, uv); // Left edge - float_t edgeY = y + tileset->tileHeight; - for(uint32_t i = 0; i < segmentsY; ++i, edgeY += tileset->tileHeight) { - spriteBatchPush( - texture, - x, edgeY, - x + tileset->tileWidth, edgeY + tileset->tileHeight, - COLOR_WHITE, - uv[0], uv[1], uv[2], uv[3] - ); - } - if(remainderY) { - spriteBatchPush( - texture, - x, edgeY, - x + tileset->tileWidth, edgeY + remainderY, - COLOR_WHITE, - uv[0], uv[1], uv[2], uv[3] - ); - } - - tilesetPositionGetUV(tileset, column + 2, row + 1, uv); // Right edge - edgeY = y + tileset->tileHeight; - for(uint32_t i = 0; i < segmentsY; ++i, edgeY += tileset->tileHeight) { - spriteBatchPush( - texture, - x + width - tileset->tileWidth, edgeY, - x + width, edgeY + tileset->tileHeight, - COLOR_WHITE, - uv[0], uv[1], uv[2], uv[3] - ); - } - if(remainderY) { - spriteBatchPush( - texture, - x + width - tileset->tileWidth, edgeY, - x + width, edgeY + remainderY, - COLOR_WHITE, - uv[0], uv[1], uv[2], uv[3] - ); - } - - // Center (Tiled) - if(!drawInner) return; - - tilesetPositionGetUV(tileset, column + 1, row + 1, uv); // Center tile - float_t centerY = y + tileset->tileHeight; - for(uint32_t j = 0; j < segmentsY; ++j, centerY += tileset->tileHeight) { - float_t centerX = x + tileset->tileWidth; - for(uint32_t i = 0; i < segmentsX; ++i, centerX += tileset->tileWidth) { - spriteBatchPush( - texture, - centerX, centerY, - centerX + tileset->tileWidth, centerY + tileset->tileHeight, - COLOR_WHITE, - uv[0], uv[1], uv[2], uv[3] - ); - } - if(remainderX) { - spriteBatchPush( - texture, - centerX, centerY, - centerX + remainderX, centerY + tileset->tileHeight, - COLOR_WHITE, - uv[0], uv[1], uv[2], uv[3] - ); - } - } - if(remainderY) { - float_t centerX = x + tileset->tileWidth; - for(uint32_t i = 0; i < segmentsX; ++i, centerX += tileset->tileWidth) { - spriteBatchPush( - texture, - centerX, centerY, - centerX + tileset->tileWidth, centerY + remainderY, - COLOR_WHITE, - uv[0], uv[1], uv[2], uv[3] - ); - } - if(remainderX) { - spriteBatchPush( - texture, - centerX, centerY, - centerX + remainderX, centerY + remainderY, - COLOR_WHITE, - uv[0], uv[1], uv[2], uv[3] - ); - } - } -} \ No newline at end of file diff --git a/archive/ui/uiframe.h b/archive/ui/uiframe.h deleted file mode 100644 index 2a9e9d01..00000000 --- a/archive/ui/uiframe.h +++ /dev/null @@ -1,61 +0,0 @@ -/** - * Copyright (c) 2025 Dominic Masters - * - * This software is released under the MIT License. - * https://opensource.org/licenses/MIT - */ - -#pragma once -#include "display/tileset/tileset.h" -#include "display/texture.h" - -/** - * Draw a UI Frame (using the 9-slice technique). - * - * @param x The x position to draw the frame at. - * @param y The y position to draw the frame at. - * @param width The width of the frame. - * @param height The height of the frame. - * @param tileset The tileset to use for rendering the frame. - * @param column The column in the tileset to use for the frame. - * @param row The row in the tileset to use for the frame. - * @param drawInner Whether to draw the inner area. - * @param texture The texture associated with the tileset. - */ -void uiFrameDraw( - const float_t x, - const float_t y, - const float_t width, - const float_t height, - const tileset_t *tileset, - const uint16_t column, - const uint16_t row, - const bool_t drawInner, - texture_t *texture -); - -/** - * Draw a tiled UI Frame (using the 9-slice technique). This will tile the - * center components to allow them to repeat without distortion. - * - * @param x The x position to draw the frame at. - * @param y The y position to draw the frame at. - * @param width The width of the frame. - * @param height The height of the frame. - * @param tileset The tileset to use for rendering the frame. - * @param column The column in the tileset to use for the frame. - * @param row The row in the tileset to use for the frame. - * @param drawInner Whether to draw the inner tiled area. - * @param texture The texture associated with the tileset. - */ -void uiFrameDrawTiled( - const float_t x, - const float_t y, - const float_t width, - const float_t height, - const tileset_t *tileset, - const uint16_t column, - const uint16_t row, - const bool_t drawInner, - texture_t *texture -); \ No newline at end of file diff --git a/archive/ui/uitext.c b/archive/ui/uitext.c deleted file mode 100644 index 2b43a2be..00000000 --- a/archive/ui/uitext.c +++ /dev/null @@ -1,111 +0,0 @@ -// /** -// * Copyright (c) 2025 Dominic Masters -// * -// * This software is released under the MIT License. -// * https://opensource.org/licenses/MIT -// */ - -#include "uitext.h" -#include "assert/assert.h" -#include "util/memory.h" -#include "display/spritebatch.h" - -void uiTextDrawChar( - const float_t x, - const float_t y, - const char_t c, - const color_t color, - const tileset_t *tileset, - texture_t *texture -) { - int32_t tileIndex = (int32_t)(c) - UI_TEXT_CHAR_START; - if(tileIndex < 0 || tileIndex >= tileset->tileCount) { - tileIndex = ((int32_t)'@') - UI_TEXT_CHAR_START; - } - - assertTrue( - tileIndex >= 0 && tileIndex <= tileset->tileCount, - "Character is out of bounds for font tiles" - ); - - vec4 uv; - tilesetTileGetUV(tileset, tileIndex, uv); - - spriteBatchPush( - texture, - x, y, - x + tileset->tileWidth, - y + tileset->tileHeight, - color, - uv[0], uv[1], uv[2], uv[3] - ); -} - -void uiTextDraw( - const float_t x, - const float_t y, - const char_t *text, - const color_t color, - const tileset_t *tileset, - texture_t *texture -) { - assertNotNull(text, "Text cannot be NULL"); - - float_t posX = x; - float_t posY = y; - - char_t c; - int32_t i = 0; - while((c = text[i++]) != '\0') { - if(c == '\n') { - posX = x; - posY += tileset->tileHeight; - continue; - } - - if(c == ' ') { - posX += tileset->tileWidth; - continue; - } - - uiTextDrawChar(posX, posY, c, color, tileset, texture); - posX += tileset->tileWidth; - } -} - -void uiTextMeasure( - const char_t *text, - const tileset_t *tileset, - int32_t *outWidth, - int32_t *outHeight -) { - assertNotNull(text, "Text cannot be NULL"); - assertNotNull(outWidth, "Output width pointer cannot be NULL"); - assertNotNull(outHeight, "Output height pointer cannot be NULL"); - - int32_t width = 0; - int32_t height = tileset->tileHeight; - int32_t lineWidth = 0; - - char_t c; - int32_t i = 0; - while((c = text[i++]) != '\0') { - if(c == '\n') { - if(lineWidth > width) { - width = lineWidth; - } - lineWidth = 0; - height += tileset->tileHeight; - continue; - } - - lineWidth += tileset->tileWidth; - } - - if(lineWidth > width) { - width = lineWidth; - } - - *outWidth = width; - *outHeight = height; -} \ No newline at end of file diff --git a/archive/ui/uitext.h b/archive/ui/uitext.h deleted file mode 100644 index 210f051e..00000000 --- a/archive/ui/uitext.h +++ /dev/null @@ -1,66 +0,0 @@ -/** - * Copyright (c) 2025 Dominic Masters - * - * This software is released under the MIT License. - * https://opensource.org/licenses/MIT - */ - -#pragma once -#include "asset/asset.h" -#include "display/texture.h" -#include "display/tileset/tileset_minogram.h" - -#define UI_TEXT_CHAR_START '!' - -/** - * Draws a single character at the specified position. - * - * @param x The x-coordinate to draw the character at. - * @param y The y-coordinate to draw the character at. - * @param c The character to draw. - * @param color The color to draw the character in. - * @param tileset Font tileset to use for rendering. - * @param texture Texture containing the font tileset image. - */ -void uiTextDrawChar( - const float_t x, - const float_t y, - const char_t c, - const color_t color, - const tileset_t *tileset, - texture_t *texture -); - -/** - * Draws a string of text at the specified position. - * - * @param x The x-coordinate to draw the text at. - * @param y The y-coordinate to draw the text at. - * @param text The null-terminated string of text to draw. - * @param color The color to draw the text in. - * @param tileset Font tileset to use for rendering. - * @param texture Texture containing the font tileset image. - */ -void uiTextDraw( - const float_t x, - const float_t y, - const char_t *text, - const color_t color, - const tileset_t *tileset, - texture_t *texture -); - -/** - * Measures the width and height of the given text string when rendered. - * - * @param text The null-terminated string of text to measure. - * @param tileset Font tileset to use for measurement. - * @param outWidth Pointer to store the measured width in pixels. - * @param outHeight Pointer to store the measured height in pixels. - */ -void uiTextMeasure( - const char_t *text, - const tileset_t *tileset, - int32_t *outWidth, - int32_t *outHeight -); \ No newline at end of file diff --git a/archive/ui/uitextbox.c b/archive/ui/uitextbox.c deleted file mode 100644 index 3aecfa85..00000000 --- a/archive/ui/uitextbox.c +++ /dev/null @@ -1,45 +0,0 @@ -/** - * Copyright (c) 2025 Dominic Masters - * - * This software is released under the MIT License. - * https://opensource.org/licenses/MIT - */ - -#include "uitextbox.h" -#include "ui/ui.h" -#include "ui/uitext.h" -// #include "rpg/rpgtextbox.h" -#include "display/screen.h" -#include "display/spritebatch.h" -#include "input/input.h" - -// void uiTextboxUpdate() { -// if(!rpgTextboxIsVisible()) return; - -// if(inputPressed(INPUT_ACTION_ACCEPT)) { -// rpgTextboxHide(); -// } -// } - -// void uiTextboxRender() { -// if(!rpgTextboxIsVisible()) return; - -// const char_t *text = RPG_TEXTBOX.text; -// int32_t textWidth, textHeight; - -// uiTextMeasure(text, UI.fontTileset, &textWidth, &textHeight); - -// float_t y = 0; -// if(RPG_TEXTBOX.position == RPG_TEXTBOX_POS_BOTTOM) { -// y = SCREEN.height - (float_t)textHeight; -// } - -// spriteBatchPush( -// NULL, -// 0.0f, y, -// (float_t)SCREEN.width, (float_t)(y + textHeight), -// COLOR_BLACK, -// 0.0f, 0.0f, 1.0f, 1.0f -// ); -// uiTextDraw(0, y, text, COLOR_RED, UI.fontTileset, &UI.fontTexture); -// } \ No newline at end of file diff --git a/archive/ui/uitextbox.h b/archive/ui/uitextbox.h deleted file mode 100644 index 09b28232..00000000 --- a/archive/ui/uitextbox.h +++ /dev/null @@ -1,19 +0,0 @@ -/** - * Copyright (c) 2025 Dominic Masters - * - * This software is released under the MIT License. - * https://opensource.org/licenses/MIT - */ - -#pragma once -#include "dusk.h" - -/** - * Updates the RPG textbox state. - */ -void uiTextboxUpdate(); - -/** - * Draws the RPG textbox if it is visible. - */ -void uiTextboxRender(); \ No newline at end of file diff --git a/archive/ui2/CMakeLists.txt b/archive/ui2/CMakeLists.txt deleted file mode 100644 index 9b35b01d..00000000 --- a/archive/ui2/CMakeLists.txt +++ /dev/null @@ -1,9 +0,0 @@ -# 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 -) \ No newline at end of file diff --git a/archive/ui2/ui2.c b/archive/ui2/ui2.c deleted file mode 100644 index df850eb3..00000000 --- a/archive/ui2/ui2.c +++ /dev/null @@ -1,8 +0,0 @@ -/** - * Copyright (c) 2026 Dominic Masters - * - * This software is released under the MIT License. - * https://opensource.org/licenses/MIT - */ - -#include "ui2.h" \ No newline at end of file diff --git a/archive/ui2/ui2.h b/archive/ui2/ui2.h deleted file mode 100644 index 02339b41..00000000 --- a/archive/ui2/ui2.h +++ /dev/null @@ -1,10 +0,0 @@ -// Copyright (c) 2026 Dominic Masters -// -// This software is released under the MIT License. -// https://opensource.org/licenses/MIT - -#pragma once -#include "dusk.h" - -// Two kinds of UI elements, those that are scripted (with a state, logic, etc) -// and those that are draw only (rectangle, text, etc). \ No newline at end of file diff --git a/src/dusk/display/spritebatch/CMakeLists.txt b/src/dusk/display/spritebatch/CMakeLists.txt index 5897115a..20ba6f23 100644 --- a/src/dusk/display/spritebatch/CMakeLists.txt +++ b/src/dusk/display/spritebatch/CMakeLists.txt @@ -7,4 +7,5 @@ target_sources(${DUSK_LIBRARY_TARGET_NAME} PUBLIC spritebatch.c + spritebatchsprite.c ) \ No newline at end of file diff --git a/src/dusk/display/spritebatch/spritebatch.h b/src/dusk/display/spritebatch/spritebatch.h index 88e7475d..ee816aa5 100644 --- a/src/dusk/display/spritebatch/spritebatch.h +++ b/src/dusk/display/spritebatch/spritebatch.h @@ -6,6 +6,7 @@ */ #pragma once +#include "spritebatchsprite.h" #include "display/mesh/quad.h" #include "display/texture/texture.h" #include "display/shader/shadermaterial.h" @@ -17,13 +18,6 @@ SPRITEBATCH_SPRITES_MAX / SPRITEBATCH_FLUSH_COUNT \ ) -typedef struct { - vec3 min; - vec3 max; - vec2 uvMin; - vec2 uvMax; -} spritebatchsprite_t; - typedef struct { mesh_t mesh; int32_t spriteCount; diff --git a/src/dusk/display/spritebatch/spritebatchsprite.c b/src/dusk/display/spritebatch/spritebatchsprite.c new file mode 100644 index 00000000..bc0437db --- /dev/null +++ b/src/dusk/display/spritebatch/spritebatchsprite.c @@ -0,0 +1,41 @@ +/** + * Copyright (c) 2026 Dominic Masters + * + * This software is released under the MIT License. + * https://opensource.org/licenses/MIT + */ + +#include "spritebatchsprite.h" +#include "util/memory.h" + +spritebatchsprite_t spriteBatchSpriteTilesetPosition( + const tileset_t *tileset, + const uint16_t column, + const uint16_t row, + const float_t x, + const float_t y, + const float_t width, + const float_t height +) { + spritebatchsprite_t sprite; + if(width == 0 || height == 0) { + memoryZero(sprite.min, sizeof(vec3)); + memoryZero(sprite.max, sizeof(vec3)); + return sprite; + } + + vec4 uv; + tilesetPositionGetUV(tileset, column, row, uv); + + sprite.min[0] = x; + sprite.min[1] = y; + sprite.min[2] = 0.0f; + sprite.max[0] = x + width; + sprite.max[1] = y + height; + sprite.max[2] = 0.0f; + sprite.uvMin[0] = uv[0]; + sprite.uvMin[1] = uv[1]; + sprite.uvMax[0] = uv[2]; + sprite.uvMax[1] = uv[3]; + return sprite; +} diff --git a/src/dusk/display/spritebatch/spritebatchsprite.h b/src/dusk/display/spritebatch/spritebatchsprite.h new file mode 100644 index 00000000..ef7e2924 --- /dev/null +++ b/src/dusk/display/spritebatch/spritebatchsprite.h @@ -0,0 +1,40 @@ +/** + * Copyright (c) 2026 Dominic Masters + * + * This software is released under the MIT License. + * https://opensource.org/licenses/MIT + */ + +#pragma once +#include "dusk.h" +#include "display/texture/tileset.h" + +typedef struct { + vec3 min; + vec3 max; + vec2 uvMin; + vec2 uvMax; +} spritebatchsprite_t; + +/** + * Builds a sprite from a tileset tile at a given column/row, positioned and + * sized in screen space. + * + * @param tileset Tileset to read UV coordinates from. + * @param column Column of the tile within the tileset. + * @param row Row of the tile within the tileset. + * @param x Screen x position of the sprite's top-left corner. + * @param y Screen y position of the sprite's top-left corner. + * @param width Width of the sprite in screen space. + * @param height Height of the sprite in screen space. + * @returns The populated sprite. + */ +spritebatchsprite_t spriteBatchSpriteTilesetPosition( + const tileset_t *tileset, + const uint16_t column, + const uint16_t row, + const float_t x, + const float_t y, + const float_t width, + const float_t height +); diff --git a/src/dusk/display/text/text.c b/src/dusk/display/text/text.c index 2b16229c..dde87683 100644 --- a/src/dusk/display/text/text.c +++ b/src/dusk/display/text/text.c @@ -81,6 +81,8 @@ errorret_t textDraw( font_t *font ) { assertNotNull(text, "Text cannot be NULL"); + + if(font == NULL) font = &FONT_DEFAULT; spritebatchsprite_t sprite; shadermaterial_t material = { diff --git a/src/dusk/input/input.h b/src/dusk/input/input.h index bf3238a8..b1545979 100644 --- a/src/dusk/input/input.h +++ b/src/dusk/input/input.h @@ -233,4 +233,14 @@ void inputBind(const inputbutton_t button, const inputaction_t act); * @param deadzone The deadzone threshold (0.0f to 1.0f). * @return The input value after applying the deadzone. */ -float_t inputDeadzone(const float_t rawValue, const float_t deadzone); \ No newline at end of file +float_t inputDeadzone(const float_t rawValue, const float_t deadzone); + +#ifdef DUSK_TIME_DYNAMIC + #define inputPressedIfDynamic(action) inputPressedDynamic(action) + #define inputReleasedIfDynamic(action) inputReleasedDynamic(action) + #define inputDownIfDynamic(action) inputIsDownDynamic(action) +#else + #define inputPressedIfDynamic(action) inputPressed(action) + #define inputReleasedIfDynamic(action) inputReleased(action) + #define inputDownIfDynamic(action) inputIsDown(action) +#endif \ No newline at end of file diff --git a/src/dusk/rpg/entity/player.c b/src/dusk/rpg/entity/player.c index b8f6bead..01b703e4 100644 --- a/src/dusk/rpg/entity/player.c +++ b/src/dusk/rpg/entity/player.c @@ -10,6 +10,7 @@ #include "rpg/rpgcamera.h" #include "util/memory.h" #include "time/time.h" +#include "ui/focus/uifocus.h" void playerInit(entity_t *entity) { assertNotNull(entity, "Entity pointer cannot be NULL"); @@ -17,6 +18,11 @@ void playerInit(entity_t *entity) { void playerInput(entity_t *entity) { assertNotNull(entity, "Entity pointer cannot be NULL"); + + // Can player act? + if(UI_FOCUS.count > 0) { + return; + } // Turn const playerinputdirmap_t *dirMap = PLAYER_INPUT_DIR_MAP; diff --git a/src/dusk/ui/focus/uifocus.c b/src/dusk/ui/focus/uifocus.c index c28a878b..69e2e8be 100644 --- a/src/dusk/ui/focus/uifocus.c +++ b/src/dusk/ui/focus/uifocus.c @@ -25,7 +25,7 @@ void uiFocusInit(void) { memoryZero(&UI_FOCUS, sizeof(uifocus_t)); } -void uiFocusPush( +uifocusitem_t * uiFocusPush( const uint8_t cols, const uint8_t rows, uifocusitemcallback_t selected, @@ -36,6 +36,8 @@ void uiFocusPush( UI_FOCUS.count < UI_FOCUS_STACK_MAX, "UI focus stack overflow" ); + assertTrue(cols > 0, "Focus item cols must be > 0"); + assertTrue(rows > 0, "Focus item rows must be > 0"); uifocusitem_t *item = &UI_FOCUS.items[UI_FOCUS.count]; memoryZero(item, sizeof(uifocusitem_t)); @@ -45,6 +47,7 @@ void uiFocusPush( item->changed = changed; item->closed = closed; UI_FOCUS.count++; + return item; } void uiFocusPop(void) { @@ -55,17 +58,25 @@ void uiFocusPop(void) { UI_FOCUS.count--; } -void uiFocusPopTo(const int32_t depth) { - assertTrue(depth >= 0, "Focus depth must be >= 0"); - assertTrue(depth <= UI_FOCUS.count, "Focus depth exceeds current depth"); - while(UI_FOCUS.count > depth) uiFocusPop(); +void uiFocusPopItem(uifocusitem_t *item) { + assertTrue(UI_FOCUS.count > 0, "UI focus stack underflow"); + assertTrue(item >= UI_FOCUS.items, "Item is not on the focus stack"); + assertTrue( + item < UI_FOCUS.items + UI_FOCUS.count, + "Item is not on the focus stack" + ); + + while(&UI_FOCUS.items[UI_FOCUS.count - 1] != item) { + uiFocusPop(); + } + uiFocusPop(); } -void uiFocusSelect(const uint8_t x, const uint8_t y) { +void uiFocusSetPosition(uifocusitem_t *item, const uint8_t x, const uint8_t y) { assertTrue(UI_FOCUS.count > 0, "No active focus item"); - uifocusitem_t *item = &UI_FOCUS.items[UI_FOCUS.count - 1]; assertTrue(item->cols > 0, "Focus item cols must be > 0"); assertTrue(item->rows > 0, "Focus item rows must be > 0"); + uint8_t newX = x % item->cols; uint8_t newY = y % item->rows; @@ -89,91 +100,82 @@ void uiFocusMoveDirection( uifocusitem_t *item, const uifocusdirection_t dir ) { - const uifocusdirmap_t *m = UI_FOCUS_DIR_MAP; - while(m->action != INPUT_ACTION_NULL) { - if(m->direction == dir) { - uint8_t x = (uint8_t)( - (item->x + item->cols + m->dx) % item->cols - ); - uint8_t y = (uint8_t)( - (item->y + item->rows + m->dy) % item->rows - ); - uiFocusSelect(x, y); - return; - } - m++; + for( + const uifocusdirmap_t *m = UI_FOCUS_DIR_MAP; + m->action != INPUT_ACTION_NULL; + m++ + ) { + if(m->direction != dir) continue; + + uint8_t x = (uint8_t)(item->x + m->dx); + uint8_t y = (uint8_t)(item->y + m->dy); + uiFocusSetPosition(item, x, y); + break; } } void uiFocusUpdate(void) { if(UI_FOCUS.count == 0) return; + + // Current item uifocusitem_t *item = &UI_FOCUS.items[UI_FOCUS.count - 1]; - #ifdef DUSK_TIME_DYNAMIC - #define PRESSED(a) inputPressedDynamic(a) - #define IS_DOWN(a) inputIsDownDynamic(a) - #else - #define PRESSED(a) inputPressed(a) - #define IS_DOWN(a) inputIsDown(a) - #endif - - if(PRESSED(INPUT_ACTION_ACCEPT)) { + // Accept + if(inputPressedIfDynamic(INPUT_ACTION_ACCEPT)) { if(item->selected != NULL) item->selected(item); - goto done; + return; } - if(PRESSED(INPUT_ACTION_CANCEL)) { - if(item->closed != NULL && item->closed(item)) { - UI_FOCUS.count--; - } - goto done; + // Return + if(inputPressedIfDynamic(INPUT_ACTION_CANCEL)) { + uiFocusPop(); + return; } - { + // Handle pressed directions + for( const uifocusdirmap_t *m = UI_FOCUS_DIR_MAP; - while(m->action != INPUT_ACTION_NULL) { - if(PRESSED(m->action)) { - UI_FOCUS.direction = m->direction; - UI_FOCUS.timeHeld = 0.0f; - uiFocusMoveDirection(item, m->direction); - goto done; - } - m++; - } + m->action != INPUT_ACTION_NULL; + m++ + ) { + if(!inputPressedIfDynamic(m->action)) continue; + + UI_FOCUS.direction = m->direction; + UI_FOCUS.timeHeld = 0.0f; + uiFocusMoveDirection(item, m->direction); + return; } - { - bool_t held = false; + // Handle held directions + bool_t held = false; + for( const uifocusdirmap_t *m = UI_FOCUS_DIR_MAP; - while(m->action != INPUT_ACTION_NULL) { - if(m->direction == UI_FOCUS.direction) { - held = IS_DOWN(m->action); - break; - } - m++; - } + m->action != INPUT_ACTION_NULL; + m++ + ) { + if(m->direction != UI_FOCUS.direction) continue; - if(!held) { - UI_FOCUS.direction = UI_FOCUS_DIRECTION_NONE; - UI_FOCUS.timeHeld = 0.0f; - goto done; - } + held = inputDownIfDynamic(m->action); + break; } + // Are we still holding? + if(!held) { + UI_FOCUS.direction = UI_FOCUS_DIRECTION_NONE; + UI_FOCUS.timeHeld = 0.0f; + return; + } + + #ifdef DUSK_TIME_DYNAMIC UI_FOCUS.timeHeld += TIME.dynamicDelta; #else UI_FOCUS.timeHeld += TIME.delta; #endif - if(UI_FOCUS.timeHeld < UI_FOCUS_HOLD_DELAY) goto done; - - if(UI_FOCUS.timeHeld >= UI_FOCUS_HOLD_DELAY + UI_FOCUS_HOLD_REPEAT) { - UI_FOCUS.timeHeld = UI_FOCUS_HOLD_DELAY; - uiFocusMoveDirection(item, UI_FOCUS.direction); - } - -done: - #undef PRESSED - #undef IS_DOWN + // Can tick? + if(UI_FOCUS.timeHeld < UI_FOCUS_HOLD_DELAY) return; + if(UI_FOCUS.timeHeld < UI_FOCUS_HOLD_DELAY + UI_FOCUS_HOLD_REPEAT) return; + UI_FOCUS.timeHeld = UI_FOCUS_HOLD_DELAY; + uiFocusMoveDirection(item, UI_FOCUS.direction); } diff --git a/src/dusk/ui/focus/uifocus.h b/src/dusk/ui/focus/uifocus.h index 24dc6ed7..1c69edef 100644 --- a/src/dusk/ui/focus/uifocus.h +++ b/src/dusk/ui/focus/uifocus.h @@ -50,7 +50,7 @@ extern const uifocusdirmap_t UI_FOCUS_DIR_MAP[]; */ typedef struct { uifocusitem_t items[UI_FOCUS_STACK_MAX]; - int32_t count; + uint8_t count; uifocusdirection_t direction; float_t timeHeld; } uifocus_t; @@ -71,8 +71,9 @@ void uiFocusInit(void); * @param selected Called when the user selects the focused cell. * @param changed Called when the focused cell position changes. * @param closed Called when this focus item is popped. + * @returns Pointer to the newly pushed focus item. */ -void uiFocusPush( +uifocusitem_t * uiFocusPush( const uint8_t cols, const uint8_t rows, uifocusitemcallback_t selected, @@ -87,28 +88,27 @@ void uiFocusPush( void uiFocusPop(void); /** - * Pops focus items until the stack depth reaches the given value. - * Each popped item has its closed callback invoked as normal. - * - * @param depth Target stack depth to pop back to. + * Pops an item and anything that was pushed after it from the focus stack. + * + * @param item The focus item to pop to. Must be on the stack. */ -void uiFocusPopTo(const int32_t depth); +void uiFocusPopItem(uifocusitem_t *item); /** * Manually sets the cursor position of the topmost focus item and * fires its changed callback. * - * @param x Column to move to. - * @param y Row to move to. + * @param x Column to move to. + * @param y Row to move to. */ -void uiFocusSelect(const uint8_t x, const uint8_t y); +void uiFocusSetPosition(uifocusitem_t *item, const uint8_t x, const uint8_t y); /** * Moves the topmost focus item one step in the given direction, * wrapping at the grid edges, and fires its changed callback. * * @param item The focus item to move. - * @param dir Direction to move. + * @param dir Direction to move. */ void uiFocusMoveDirection( uifocusitem_t *item, diff --git a/src/dusk/ui/frame/uiframe.c b/src/dusk/ui/frame/uiframe.c index 8608e5db..3097fcbe 100644 --- a/src/dusk/ui/frame/uiframe.c +++ b/src/dusk/ui/frame/uiframe.c @@ -19,25 +19,25 @@ errorret_t uiFrameInit(void) { memoryZero(&UI_FRAME, sizeof(uiframe_t)); // Init texture. - color_t border = color4b(0, 100, 220, 200); - color_t center = color4b(0, 100, 220, 50); + color_t border = color4b(0, 100, 220, 255); + color_t center = color4b(0, 100, 220, 200); - for(uint8_t y = 0; y < UIFRAME_DUMMY_HEIGHT; y++) { - for(uint8_t x = 0; x < UIFRAME_DUMMY_WIDTH; x++) { + for(uint8_t y = 0; y < UIFRAME_BORDER_HEIGHT; y++) { + for(uint8_t x = 0; x < UIFRAME_BORDER_WIDTH; x++) { color_t c; - if(x >= 12 || y >= 12) { + if(x >= 12 || y >= 12) {// Because power of two is required. c = COLOR_TRANSPARENT; } else if(y < 4 || y >= 8 || x < 4 || x >= 8) { c = border; } else { c = center; } - UI_FRAME.pixels[y * UIFRAME_DUMMY_WIDTH + x] = c; + UI_FRAME.pixels[y * UIFRAME_BORDER_WIDTH + x] = c; } } errorChain(textureInit( - &UI_FRAME.texture, UIFRAME_DUMMY_WIDTH, UIFRAME_DUMMY_HEIGHT, + &UI_FRAME.texture, UIFRAME_BORDER_WIDTH, UIFRAME_BORDER_HEIGHT, TEXTURE_FORMAT_RGBA, (texturedata_t){ .rgbaColors = UI_FRAME.pixels } )); @@ -47,8 +47,8 @@ errorret_t uiFrameInit(void) { UI_FRAME.tileset.columns = 3; UI_FRAME.tileset.rows = 3; UI_FRAME.tileset.tileCount = 9; - UI_FRAME.tileset.uv[0] = 4.0f / UIFRAME_DUMMY_WIDTH; - UI_FRAME.tileset.uv[1] = 4.0f / UIFRAME_DUMMY_HEIGHT; + UI_FRAME.tileset.uv[0] = 4.0f / UIFRAME_BORDER_WIDTH; + UI_FRAME.tileset.uv[1] = 4.0f / UIFRAME_BORDER_HEIGHT; errorOk(); } @@ -68,115 +68,44 @@ errorret_t uiFrameDraw( spritebatchsprite_t sprites[9]; float_t tileW = (float_t)UI_FRAME.tileset.tileWidth; float_t tileH = (float_t)UI_FRAME.tileset.tileHeight; - vec4 uv; - tilesetPositionGetUV(&UI_FRAME.tileset, 0, 0, uv); - sprites[0].min[0] = x; - sprites[0].min[1] = y; - sprites[0].min[2] = 0.0f; - sprites[0].max[0] = x + tileW; - sprites[0].max[1] = y + tileH; - sprites[0].max[2] = 0.0f; - sprites[0].uvMin[0] = uv[0]; - sprites[0].uvMin[1] = uv[1]; - sprites[0].uvMax[0] = uv[2]; - sprites[0].uvMax[1] = uv[3]; - - tilesetPositionGetUV(&UI_FRAME.tileset, 1, 0, uv); - sprites[1].min[0] = x + tileW; - sprites[1].min[1] = y; - sprites[1].min[2] = 0.0f; - sprites[1].max[0] = x + width - tileW; - sprites[1].max[1] = y + tileH; - sprites[1].max[2] = 0.0f; - sprites[1].uvMin[0] = uv[0]; - sprites[1].uvMin[1] = uv[1]; - sprites[1].uvMax[0] = uv[2]; - sprites[1].uvMax[1] = uv[3]; - - tilesetPositionGetUV(&UI_FRAME.tileset, 2, 0, uv); - sprites[2].min[0] = x + width - tileW; - sprites[2].min[1] = y; - sprites[2].min[2] = 0.0f; - sprites[2].max[0] = x + width; - sprites[2].max[1] = y + tileH; - sprites[2].max[2] = 0.0f; - sprites[2].uvMin[0] = uv[0]; - sprites[2].uvMin[1] = uv[1]; - sprites[2].uvMax[0] = uv[2]; - sprites[2].uvMax[1] = uv[3]; - - tilesetPositionGetUV(&UI_FRAME.tileset, 0, 1, uv); - sprites[3].min[0] = x; - sprites[3].min[1] = y + tileH; - sprites[3].min[2] = 0.0f; - sprites[3].max[0] = x + tileW; - sprites[3].max[1] = y + height - tileH; - sprites[3].max[2] = 0.0f; - sprites[3].uvMin[0] = uv[0]; - sprites[3].uvMin[1] = uv[1]; - sprites[3].uvMax[0] = uv[2]; - sprites[3].uvMax[1] = uv[3]; - - tilesetPositionGetUV(&UI_FRAME.tileset, 1, 1, uv); - sprites[4].min[0] = x + tileW; - sprites[4].min[1] = y + tileH; - sprites[4].min[2] = 0.0f; - sprites[4].max[0] = x + width - tileW; - sprites[4].max[1] = y + height - tileH; - sprites[4].max[2] = 0.0f; - sprites[4].uvMin[0] = uv[0]; - sprites[4].uvMin[1] = uv[1]; - sprites[4].uvMax[0] = uv[2]; - sprites[4].uvMax[1] = uv[3]; - - tilesetPositionGetUV(&UI_FRAME.tileset, 2, 1, uv); - sprites[5].min[0] = x + width - tileW; - sprites[5].min[1] = y + tileH; - sprites[5].min[2] = 0.0f; - sprites[5].max[0] = x + width; - sprites[5].max[1] = y + height - tileH; - sprites[5].max[2] = 0.0f; - sprites[5].uvMin[0] = uv[0]; - sprites[5].uvMin[1] = uv[1]; - sprites[5].uvMax[0] = uv[2]; - sprites[5].uvMax[1] = uv[3]; - - tilesetPositionGetUV(&UI_FRAME.tileset, 0, 2, uv); - sprites[6].min[0] = x; - sprites[6].min[1] = y + height - tileH; - sprites[6].min[2] = 0.0f; - sprites[6].max[0] = x + tileW; - sprites[6].max[1] = y + height; - sprites[6].max[2] = 0.0f; - sprites[6].uvMin[0] = uv[0]; - sprites[6].uvMin[1] = uv[1]; - sprites[6].uvMax[0] = uv[2]; - sprites[6].uvMax[1] = uv[3]; - - tilesetPositionGetUV(&UI_FRAME.tileset, 1, 2, uv); - sprites[7].min[0] = x + tileW; - sprites[7].min[1] = y + height - tileH; - sprites[7].min[2] = 0.0f; - sprites[7].max[0] = x + width - tileW; - sprites[7].max[1] = y + height; - sprites[7].max[2] = 0.0f; - sprites[7].uvMin[0] = uv[0]; - sprites[7].uvMin[1] = uv[1]; - sprites[7].uvMax[0] = uv[2]; - sprites[7].uvMax[1] = uv[3]; - - tilesetPositionGetUV(&UI_FRAME.tileset, 2, 2, uv); - sprites[8].min[0] = x + width - tileW; - sprites[8].min[1] = y + height - tileH; - sprites[8].min[2] = 0.0f; - sprites[8].max[0] = x + width; - sprites[8].max[1] = y + height; - sprites[8].max[2] = 0.0f; - sprites[8].uvMin[0] = uv[0]; - sprites[8].uvMin[1] = uv[1]; - sprites[8].uvMax[0] = uv[2]; - sprites[8].uvMax[1] = uv[3]; + sprites[0] = spriteBatchSpriteTilesetPosition( + &UI_FRAME.tileset, 0, 0, + x, y, tileW, tileH + ); + sprites[1] = spriteBatchSpriteTilesetPosition( + &UI_FRAME.tileset, 1, 0, + x + tileW, y, width - (tileW * 2.0f), tileH + ); + sprites[2] = spriteBatchSpriteTilesetPosition( + &UI_FRAME.tileset, 2, 0, + x + width - tileW, y, tileW, tileH + ); + sprites[3] = spriteBatchSpriteTilesetPosition( + &UI_FRAME.tileset, 0, 1, + x, y + tileH, tileW, height - (tileH * 2.0f) + ); + sprites[4] = spriteBatchSpriteTilesetPosition( + &UI_FRAME.tileset, 1, 1, + x + tileW, y + tileH, + width - (tileW * 2.0f), height - (tileH * 2.0f) + ); + sprites[5] = spriteBatchSpriteTilesetPosition( + &UI_FRAME.tileset, 2, 1, + x + width - tileW, y + tileH, tileW, height - (tileH * 2.0f) + ); + sprites[6] = spriteBatchSpriteTilesetPosition( + &UI_FRAME.tileset, 0, 2, + x, y + height - tileH, tileW, tileH + ); + sprites[7] = spriteBatchSpriteTilesetPosition( + &UI_FRAME.tileset, 1, 2, + x + tileW, y + height - tileH, width - (tileW * 2.0f), tileH + ); + sprites[8] = spriteBatchSpriteTilesetPosition( + &UI_FRAME.tileset, 2, 2, + x + width - tileW, y + height - tileH, tileW, tileH + ); return spriteBatchBuffer(sprites, 9, &SHADER_UNLIT, material); } diff --git a/src/dusk/ui/frame/uiframe.h b/src/dusk/ui/frame/uiframe.h index 64c45274..20511a2e 100644 --- a/src/dusk/ui/frame/uiframe.h +++ b/src/dusk/ui/frame/uiframe.h @@ -10,13 +10,13 @@ #include "display/texture/texture.h" #include "display/texture/tileset.h" -#define UIFRAME_DUMMY_WIDTH 16 -#define UIFRAME_DUMMY_HEIGHT 16 +#define UIFRAME_BORDER_WIDTH 16 +#define UIFRAME_BORDER_HEIGHT 16 typedef struct { tileset_t tileset; texture_t texture; - color_t pixels[UIFRAME_DUMMY_WIDTH * UIFRAME_DUMMY_HEIGHT]; + color_t pixels[UIFRAME_BORDER_WIDTH * UIFRAME_BORDER_HEIGHT]; } uiframe_t; extern uiframe_t UI_FRAME; diff --git a/src/dusk/ui/frame/uisettings.c b/src/dusk/ui/frame/uisettings.c index 7408e79a..a27f4966 100644 --- a/src/dusk/ui/frame/uisettings.c +++ b/src/dusk/ui/frame/uisettings.c @@ -8,6 +8,9 @@ #include "uisettings.h" #include "ui/frame/uiframe.h" #include "util/memory.h" +#include "display/spritebatch/spritebatch.h" +#include "display/text/text.h" +#include "input/input.h" uisettings_t UI_SETTINGS; @@ -17,18 +20,60 @@ errorret_t uiSettingsInit(void) { } errorret_t uiSettingsUpdate(void) { - // if(!UI_SETTINGS.visible) errorOk(); + if(!inputPressedIfDynamic(INPUT_ACTION_PAUSE)) errorOk(); + + if(UI_SETTINGS.item == NULL) { + uiSettingsOpen(); + } else { + uiSettingsClose(); + } + errorOk(); } errorret_t uiSettingsDraw(void) { - // if(!UI_SETTINGS.visible) errorOk(); + if(UI_SETTINGS.item == NULL) errorOk(); + + const char_t *texts[] = { + "Settings", + "Settings two", + "Settings three" + }; + const uint8_t textCount = sizeof(texts) / sizeof(texts[0]); + errorChain(uiFrameDraw( - 100.0f, 100.0f, 1.0f, 1.0f + 0.0f, 0.0f, 100.0f, FONT_DEFAULT.tileset->tileHeight * textCount + (UIFRAME_BORDER_HEIGHT * 2) )); + + for(uint8_t i = 0; i < textCount; i++) { + const char_t *c = texts[i]; + errorChain(textDraw( + UIFRAME_BORDER_WIDTH, + UIFRAME_BORDER_HEIGHT + (i * FONT_DEFAULT.tileset->tileHeight), + c, + UI_SETTINGS.item->y == i ? COLOR_RED : COLOR_WHITE, NULL + )); + } + + errorChain(spriteBatchFlush()); errorOk(); } +void uiSettingsOpen() { + if(UI_SETTINGS.item != NULL) return; + + UI_SETTINGS.item = uiFocusPush( + 1, 3, NULL, NULL, NULL + ); +} + +void uiSettingsClose() { + if(UI_SETTINGS.item == NULL) return; + + uiFocusPopItem(UI_SETTINGS.item); + UI_SETTINGS.item = NULL; +} + errorret_t uiSettingsDispose(void) { errorOk(); } diff --git a/src/dusk/ui/frame/uisettings.h b/src/dusk/ui/frame/uisettings.h index bc7f2d24..6ba12013 100644 --- a/src/dusk/ui/frame/uisettings.h +++ b/src/dusk/ui/frame/uisettings.h @@ -7,9 +7,10 @@ #pragma once #include "error/error.h" +#include "ui/focus/uifocus.h" typedef struct { - bool_t visible; + uifocusitem_t *item; } uisettings_t; extern uisettings_t UI_SETTINGS; @@ -35,6 +36,16 @@ errorret_t uiSettingsUpdate(void); */ errorret_t uiSettingsDraw(void); +/** + * Opens the settings panel. No-op when already open. + */ +void uiSettingsOpen(); + +/** + * Closes the settings panel. No-op when already closed. + */ +void uiSettingsClose(); + /** * Disposes of the settings panel. * -- 2.52.0 From d3715eece8ae363dd237c50c1ac836615bf9c434 Mon Sep 17 00:00:00 2001 From: Dominic Masters Date: Thu, 25 Jun 2026 18:16:46 -0500 Subject: [PATCH 06/16] Time and tick helpers --- src/dusk/rpg/entity/entityanim.h | 8 ++++---- src/dusk/time/time.h | 16 ++++++++++++++++ 2 files changed, 20 insertions(+), 4 deletions(-) diff --git a/src/dusk/rpg/entity/entityanim.h b/src/dusk/rpg/entity/entityanim.h index ccd425e6..91390682 100644 --- a/src/dusk/rpg/entity/entityanim.h +++ b/src/dusk/rpg/entity/entityanim.h @@ -6,11 +6,11 @@ */ #pragma once -#include "dusk.h" +#include "time/time.h" -#define ENTITY_ANIM_TURN_DURATION 0.06f -#define ENTITY_ANIM_WALK_DURATION 0.1f -#define ENTITY_ANIM_RUN_DURATION 0.05f +#define ENTITY_ANIM_TURN_DURATION TIME_TICKS_TO_TIME(2) +#define ENTITY_ANIM_WALK_DURATION TIME_TICKS_TO_TIME(6) +#define ENTITY_ANIM_RUN_DURATION TIME_TICKS_TO_TIME(3) typedef struct entity_s entity_t; diff --git a/src/dusk/time/time.h b/src/dusk/time/time.h index a4b38f25..68f53537 100644 --- a/src/dusk/time/time.h +++ b/src/dusk/time/time.h @@ -13,6 +13,22 @@ #define DUSK_TIME_STEP (16.0f / 1000.0f) #endif +/** + * Converts a fixed-step tick count to a duration in seconds. + * + * @param ticks The number of ticks to convert. + * @return The duration in seconds. + */ +#define TIME_TICKS_TO_TIME(ticks) ((float_t)(ticks) * DUSK_TIME_STEP) + +/** + * Converts a duration in seconds to a fixed-step tick count. + * + * @param seconds The duration in seconds to convert. + * @return The number of ticks. + */ +#define TIME_TIME_TO_TICKS(seconds) ((int32_t)((seconds) / DUSK_TIME_STEP)) + #ifdef DUSK_TIME_DYNAMIC #ifndef timeTickPlatform #error "DUSK_TIME_DYNAMIC needs tick method defined" -- 2.52.0 From ce29435831454bf671c49ba0b474216899ee83d2 Mon Sep 17 00:00:00 2001 From: Dominic Masters Date: Thu, 25 Jun 2026 18:34:49 -0500 Subject: [PATCH 07/16] Example settings more or less done, showing full ui example basically. --- src/dusk/rpg/entity/player.c | 15 ++++-- src/dusk/ui/CMakeLists.txt | 1 + src/dusk/ui/focus/uifocus.c | 1 + src/dusk/ui/frame/uisettings.c | 47 ++++++++++++------ src/dusk/ui/frame/uisettings.h | 16 +++--- src/dusk/ui/uielement.c | 1 - src/dusk/ui/widget/CMakeLists.txt | 9 ++++ src/dusk/ui/widget/uicheckbox.c | 58 ++++++++++++++++++++++ src/dusk/ui/widget/uicheckbox.h | 81 +++++++++++++++++++++++++++++++ 9 files changed, 202 insertions(+), 27 deletions(-) create mode 100644 src/dusk/ui/widget/CMakeLists.txt create mode 100644 src/dusk/ui/widget/uicheckbox.c create mode 100644 src/dusk/ui/widget/uicheckbox.h diff --git a/src/dusk/rpg/entity/player.c b/src/dusk/rpg/entity/player.c index 01b703e4..8a8b8265 100644 --- a/src/dusk/rpg/entity/player.c +++ b/src/dusk/rpg/entity/player.c @@ -11,6 +11,7 @@ #include "util/memory.h" #include "time/time.h" #include "ui/focus/uifocus.h" +#include "ui/frame/uisettings.h" void playerInit(entity_t *entity) { assertNotNull(entity, "Entity pointer cannot be NULL"); @@ -19,11 +20,19 @@ void playerInit(entity_t *entity) { void playerInput(entity_t *entity) { assertNotNull(entity, "Entity pointer cannot be NULL"); - // Can player act? - if(UI_FOCUS.count > 0) { - return; + // Toggle settings on pause + if(uiSettingsIsOpen() && inputPressed(INPUT_ACTION_PAUSE)) { + uiSettingsClose(); + } else if( + inputPressed(INPUT_ACTION_PAUSE) && + entity->animation == ENTITY_ANIM_IDLE + ) { + uiSettingsOpen(); } + // Can player act? + if(UI_FOCUS.count > 0) return; + // Turn const playerinputdirmap_t *dirMap = PLAYER_INPUT_DIR_MAP; do { diff --git a/src/dusk/ui/CMakeLists.txt b/src/dusk/ui/CMakeLists.txt index 524c4a35..0c3b196e 100644 --- a/src/dusk/ui/CMakeLists.txt +++ b/src/dusk/ui/CMakeLists.txt @@ -7,6 +7,7 @@ add_subdirectory(debug) add_subdirectory(frame) add_subdirectory(focus) add_subdirectory(overlay) +add_subdirectory(widget) # Sources target_sources(${DUSK_LIBRARY_TARGET_NAME} diff --git a/src/dusk/ui/focus/uifocus.c b/src/dusk/ui/focus/uifocus.c index 69e2e8be..73b548b4 100644 --- a/src/dusk/ui/focus/uifocus.c +++ b/src/dusk/ui/focus/uifocus.c @@ -47,6 +47,7 @@ uifocusitem_t * uiFocusPush( item->changed = changed; item->closed = closed; UI_FOCUS.count++; + if(item->changed != NULL) item->changed(item); return item; } diff --git a/src/dusk/ui/frame/uisettings.c b/src/dusk/ui/frame/uisettings.c index a27f4966..46058a0c 100644 --- a/src/dusk/ui/frame/uisettings.c +++ b/src/dusk/ui/frame/uisettings.c @@ -7,27 +7,31 @@ #include "uisettings.h" #include "ui/frame/uiframe.h" +#include "ui/widget/uicheckbox.h" #include "util/memory.h" #include "display/spritebatch/spritebatch.h" #include "display/text/text.h" -#include "input/input.h" uisettings_t UI_SETTINGS; -errorret_t uiSettingsInit(void) { - memoryZero(&UI_SETTINGS, sizeof(uisettings_t)); - errorOk(); +bool_t uiSettingsSelected(const uifocusitem_t *item) { + if(item->y == 3) uiCheckboxToggle(&UI_SETTINGS.checkboxTest); + return true; } -errorret_t uiSettingsUpdate(void) { - if(!inputPressedIfDynamic(INPUT_ACTION_PAUSE)) errorOk(); - - if(UI_SETTINGS.item == NULL) { - uiSettingsOpen(); - } else { - uiSettingsClose(); - } +bool_t uiSettingsChanged(const uifocusitem_t *item) { + uiCheckboxSetHighlighted(&UI_SETTINGS.checkboxTest, item->y == 3); + return true; +} +bool_t uiSettingsClosed(const uifocusitem_t *item) { + UI_SETTINGS.item = NULL; + return true; +} + +errorret_t uiSettingsInit(void) { + memoryZero(&UI_SETTINGS, sizeof(uisettings_t)); + uiCheckboxInit(&UI_SETTINGS.checkboxTest, "Enable thing?"); errorOk(); } @@ -40,30 +44,41 @@ errorret_t uiSettingsDraw(void) { "Settings three" }; const uint8_t textCount = sizeof(texts) / sizeof(texts[0]); + const uint8_t totalRows = textCount + 1; errorChain(uiFrameDraw( - 0.0f, 0.0f, 100.0f, FONT_DEFAULT.tileset->tileHeight * textCount + (UIFRAME_BORDER_HEIGHT * 2) + 0.0f, 0.0f, 100.0f, + FONT_DEFAULT.tileset->tileHeight * totalRows + (UIFRAME_BORDER_HEIGHT * 2) )); for(uint8_t i = 0; i < textCount; i++) { - const char_t *c = texts[i]; errorChain(textDraw( UIFRAME_BORDER_WIDTH, UIFRAME_BORDER_HEIGHT + (i * FONT_DEFAULT.tileset->tileHeight), - c, + texts[i], UI_SETTINGS.item->y == i ? COLOR_RED : COLOR_WHITE, NULL )); } + errorChain(uiCheckboxDraw( + &UI_SETTINGS.checkboxTest, + UIFRAME_BORDER_WIDTH, + UIFRAME_BORDER_HEIGHT + (textCount * FONT_DEFAULT.tileset->tileHeight) + )); + errorChain(spriteBatchFlush()); errorOk(); } +bool_t uiSettingsIsOpen(void) { + return UI_SETTINGS.item != NULL; +} + void uiSettingsOpen() { if(UI_SETTINGS.item != NULL) return; UI_SETTINGS.item = uiFocusPush( - 1, 3, NULL, NULL, NULL + 1, 4, uiSettingsSelected, uiSettingsChanged, uiSettingsClosed ); } diff --git a/src/dusk/ui/frame/uisettings.h b/src/dusk/ui/frame/uisettings.h index 6ba12013..7b2ef2f9 100644 --- a/src/dusk/ui/frame/uisettings.h +++ b/src/dusk/ui/frame/uisettings.h @@ -8,9 +8,11 @@ #pragma once #include "error/error.h" #include "ui/focus/uifocus.h" +#include "ui/widget/uicheckbox.h" typedef struct { uifocusitem_t *item; + uicheckbox_t checkboxTest; } uisettings_t; extern uisettings_t UI_SETTINGS; @@ -22,13 +24,6 @@ extern uisettings_t UI_SETTINGS; */ errorret_t uiSettingsInit(void); -/** - * Updates the settings panel. Handles input when visible. - * - * @return Any error that occurs. - */ -errorret_t uiSettingsUpdate(void); - /** * Draws the settings panel. No-op when not visible. * @@ -36,6 +31,13 @@ errorret_t uiSettingsUpdate(void); */ errorret_t uiSettingsDraw(void); +/** + * Returns true when the settings panel is currently open. + * + * @returns True if open. + */ +bool_t uiSettingsIsOpen(void); + /** * Opens the settings panel. No-op when already open. */ diff --git a/src/dusk/ui/uielement.c b/src/dusk/ui/uielement.c index a5dd6e31..577bcf9e 100644 --- a/src/dusk/ui/uielement.c +++ b/src/dusk/ui/uielement.c @@ -35,7 +35,6 @@ uielement_t UI_ELEMENTS[] = { { .init = uiSettingsInit, - .update = uiSettingsUpdate, .draw = uiSettingsDraw, .dispose = uiSettingsDispose }, diff --git a/src/dusk/ui/widget/CMakeLists.txt b/src/dusk/ui/widget/CMakeLists.txt new file mode 100644 index 00000000..70f9d666 --- /dev/null +++ b/src/dusk/ui/widget/CMakeLists.txt @@ -0,0 +1,9 @@ +# Copyright (c) 2026 Dominic Masters +# +# This software is released under the MIT License. +# https://opensource.org/licenses/MIT + +target_sources(${DUSK_LIBRARY_TARGET_NAME} + PUBLIC + uicheckbox.c +) diff --git a/src/dusk/ui/widget/uicheckbox.c b/src/dusk/ui/widget/uicheckbox.c new file mode 100644 index 00000000..813cce1a --- /dev/null +++ b/src/dusk/ui/widget/uicheckbox.c @@ -0,0 +1,58 @@ +/** + * Copyright (c) 2026 Dominic Masters + * + * This software is released under the MIT License. + * https://opensource.org/licenses/MIT + */ + +#include "uicheckbox.h" +#include "util/memory.h" +#include "util/string.h" +#include "display/text/text.h" +#include "display/color.h" + +void uiCheckboxInit( + uicheckbox_t *checkbox, + const char_t *label +) { + memoryZero(checkbox, sizeof(uicheckbox_t)); + stringCopy(checkbox->label, label, UI_CHECKBOX_LABEL_MAX); +} + +bool_t uiCheckboxIsChecked(const uicheckbox_t *checkbox) { + return checkbox->checked; +} + +void uiCheckboxSetChecked(uicheckbox_t *checkbox, const bool_t checked) { + checkbox->checked = checked; +} + +void uiCheckboxToggle(uicheckbox_t *checkbox) { + uiCheckboxSetChecked(checkbox, !uiCheckboxIsChecked(checkbox)); +} + +bool_t uiCheckboxIsHighlighted(const uicheckbox_t *checkbox) { + return checkbox->highlighted; +} + +void uiCheckboxSetHighlighted(uicheckbox_t *checkbox, const bool_t highlighted) { + checkbox->highlighted = highlighted; +} + +errorret_t uiCheckboxDraw( + const uicheckbox_t *checkbox, + const float_t x, + const float_t y +) { + color_t color = checkbox->highlighted ? COLOR_RED : COLOR_WHITE; + const char_t *mark = checkbox->checked ? "Y " : "N "; + errorChain(textDraw(x, y, mark, color, &FONT_DEFAULT)); + + int32_t markW, markH; + textMeasure(mark, &FONT_DEFAULT, &markW, &markH); + errorChain(textDraw( + x + (float_t)markW, y, checkbox->label, color, &FONT_DEFAULT + )); + + errorOk(); +} diff --git a/src/dusk/ui/widget/uicheckbox.h b/src/dusk/ui/widget/uicheckbox.h new file mode 100644 index 00000000..2d06b619 --- /dev/null +++ b/src/dusk/ui/widget/uicheckbox.h @@ -0,0 +1,81 @@ +/** + * Copyright (c) 2026 Dominic Masters + * + * This software is released under the MIT License. + * https://opensource.org/licenses/MIT + */ + +#pragma once +#include "error/error.h" + +#define UI_CHECKBOX_LABEL_MAX 32 + +typedef struct uicheckbox_s { + char_t label[UI_CHECKBOX_LABEL_MAX]; + bool_t checked; + bool_t highlighted; +} uicheckbox_t; + +/** + * Initializes a checkbox. + * + * @param checkbox The checkbox to initialize. + * @param label Display label, truncated to UI_CHECKBOX_LABEL_MAX - 1 chars. + */ +void uiCheckboxInit( + uicheckbox_t *checkbox, + const char_t *label +); + +/** + * Returns whether the checkbox is checked. + * + * @param checkbox The checkbox to query. + * @returns True if checked. + */ +bool_t uiCheckboxIsChecked(const uicheckbox_t *checkbox); + +/** + * Sets the checked state of the checkbox. + * + * @param checkbox The checkbox to update. + * @param checked The new checked state. + */ +void uiCheckboxSetChecked(uicheckbox_t *checkbox, const bool_t checked); + +/** + * Toggles the checked state of the checkbox. + * + * @param checkbox The checkbox to toggle. + */ +void uiCheckboxToggle(uicheckbox_t *checkbox); + +/** + * Returns whether the checkbox is highlighted. + * + * @param checkbox The checkbox to query. + * @returns True if highlighted. + */ +bool_t uiCheckboxIsHighlighted(const uicheckbox_t *checkbox); + +/** + * Sets the highlighted state of the checkbox. + * + * @param checkbox The checkbox to update. + * @param highlighted The new highlighted state. + */ +void uiCheckboxSetHighlighted(uicheckbox_t *checkbox, const bool_t highlighted); + +/** + * Draws the checkbox at the given screen position. + * + * @param checkbox The checkbox to draw. + * @param x Screen x position. + * @param y Screen y position. + * @return Any error that occurs. + */ +errorret_t uiCheckboxDraw( + const uicheckbox_t *checkbox, + const float_t x, + const float_t y +); -- 2.52.0 From d85737cc08c6da31336feeea84b3b1103be54c40 Mon Sep 17 00:00:00 2001 From: Dominic Masters Date: Thu, 25 Jun 2026 19:35:33 -0500 Subject: [PATCH 08/16] UI menu --- src/dusk/ui/focus/uifocus.c | 4 +- src/dusk/ui/focus/uifocus.h | 4 +- src/dusk/ui/focus/uifocusitem.h | 1 + src/dusk/ui/frame/uisettings.c | 93 +++++++++-------- src/dusk/ui/frame/uisettings.h | 9 +- src/dusk/ui/widget/CMakeLists.txt | 1 + src/dusk/ui/widget/uimenu.c | 163 ++++++++++++++++++++++++++++++ src/dusk/ui/widget/uimenu.h | 160 +++++++++++++++++++++++++++++ 8 files changed, 381 insertions(+), 54 deletions(-) create mode 100644 src/dusk/ui/widget/uimenu.c create mode 100644 src/dusk/ui/widget/uimenu.h diff --git a/src/dusk/ui/focus/uifocus.c b/src/dusk/ui/focus/uifocus.c index 73b548b4..1b6103e8 100644 --- a/src/dusk/ui/focus/uifocus.c +++ b/src/dusk/ui/focus/uifocus.c @@ -30,7 +30,8 @@ uifocusitem_t * uiFocusPush( const uint8_t rows, uifocusitemcallback_t selected, uifocusitemcallback_t changed, - uifocusitemcallback_t closed + uifocusitemcallback_t closed, + void *user ) { assertTrue( UI_FOCUS.count < UI_FOCUS_STACK_MAX, @@ -46,6 +47,7 @@ uifocusitem_t * uiFocusPush( item->selected = selected; item->changed = changed; item->closed = closed; + item->user = user; UI_FOCUS.count++; if(item->changed != NULL) item->changed(item); return item; diff --git a/src/dusk/ui/focus/uifocus.h b/src/dusk/ui/focus/uifocus.h index 1c69edef..9369e097 100644 --- a/src/dusk/ui/focus/uifocus.h +++ b/src/dusk/ui/focus/uifocus.h @@ -71,6 +71,7 @@ void uiFocusInit(void); * @param selected Called when the user selects the focused cell. * @param changed Called when the focused cell position changes. * @param closed Called when this focus item is popped. + * @param user Arbitrary pointer stored on the item before changed fires. * @returns Pointer to the newly pushed focus item. */ uifocusitem_t * uiFocusPush( @@ -78,7 +79,8 @@ uifocusitem_t * uiFocusPush( const uint8_t rows, uifocusitemcallback_t selected, uifocusitemcallback_t changed, - uifocusitemcallback_t closed + uifocusitemcallback_t closed, + void *user ); /** diff --git a/src/dusk/ui/focus/uifocusitem.h b/src/dusk/ui/focus/uifocusitem.h index ebe44167..431835ac 100644 --- a/src/dusk/ui/focus/uifocusitem.h +++ b/src/dusk/ui/focus/uifocusitem.h @@ -29,4 +29,5 @@ struct uifocusitem_s { uifocusitemcallback_t selected; uifocusitemcallback_t changed; uifocusitemcallback_t closed; + void *user; }; diff --git a/src/dusk/ui/frame/uisettings.c b/src/dusk/ui/frame/uisettings.c index 46058a0c..551ada08 100644 --- a/src/dusk/ui/frame/uisettings.c +++ b/src/dusk/ui/frame/uisettings.c @@ -7,63 +7,67 @@ #include "uisettings.h" #include "ui/frame/uiframe.h" -#include "ui/widget/uicheckbox.h" #include "util/memory.h" #include "display/spritebatch/spritebatch.h" #include "display/text/text.h" uisettings_t UI_SETTINGS; -bool_t uiSettingsSelected(const uifocusitem_t *item) { - if(item->y == 3) uiCheckboxToggle(&UI_SETTINGS.checkboxTest); - return true; +void uiSettingsSelected( + const uimenu_t *menu, + const uint8_t index, + const uimenuitem_t *item +) { + if(item->type == UIMENU_WIDGET_TYPE_CHECKBOX) { + uiCheckboxToggle(&UI_SETTINGS.items[index].checkbox); + } } -bool_t uiSettingsChanged(const uifocusitem_t *item) { - uiCheckboxSetHighlighted(&UI_SETTINGS.checkboxTest, item->y == 3); - return true; -} - -bool_t uiSettingsClosed(const uifocusitem_t *item) { - UI_SETTINGS.item = NULL; - return true; +void uiSettingsClosed(const uimenu_t *menu) { + // nothing extra needed; menu clears focusItem itself } errorret_t uiSettingsInit(void) { memoryZero(&UI_SETTINGS, sizeof(uisettings_t)); - uiCheckboxInit(&UI_SETTINGS.checkboxTest, "Enable thing?"); + + UI_SETTINGS.items[0] = (uimenuitem_t){ + .type = UIMENU_WIDGET_TYPE_LABEL, + .label = "Settings" + }; + UI_SETTINGS.items[1] = (uimenuitem_t){ + .type = UIMENU_WIDGET_TYPE_LABEL, + .label = "Settings two" + }; + UI_SETTINGS.items[2] = (uimenuitem_t){ + .type = UIMENU_WIDGET_TYPE_LABEL, + .label = "Settings three" + }; + UI_SETTINGS.items[3].type = UIMENU_WIDGET_TYPE_CHECKBOX; + uiCheckboxInit(&UI_SETTINGS.items[3].checkbox, "Enable thing?"); + + uiMenuInit(&UI_SETTINGS.menu, uiSettingsSelected, uiSettingsClosed, NULL); + uiMenuSetItems( + &UI_SETTINGS.menu, + UI_SETTINGS.items, + UI_SETTINGS_ITEM_COUNT, + 2 + ); + errorOk(); } errorret_t uiSettingsDraw(void) { - if(UI_SETTINGS.item == NULL) errorOk(); + if(!uiMenuIsActive(&UI_SETTINGS.menu)) errorOk(); - const char_t *texts[] = { - "Settings", - "Settings two", - "Settings three" - }; - const uint8_t textCount = sizeof(texts) / sizeof(texts[0]); - const uint8_t totalRows = textCount + 1; + uint8_t rows = UI_SETTINGS_ITEM_COUNT; - errorChain(uiFrameDraw( - 0.0f, 0.0f, 100.0f, - FONT_DEFAULT.tileset->tileHeight * totalRows + (UIFRAME_BORDER_HEIGHT * 2) - )); - - for(uint8_t i = 0; i < textCount; i++) { - errorChain(textDraw( - UIFRAME_BORDER_WIDTH, - UIFRAME_BORDER_HEIGHT + (i * FONT_DEFAULT.tileset->tileHeight), - texts[i], - UI_SETTINGS.item->y == i ? COLOR_RED : COLOR_WHITE, NULL - )); - } - - errorChain(uiCheckboxDraw( - &UI_SETTINGS.checkboxTest, + errorChain(uiFrameDraw(0.0f, 0.0f, 300.0f, 300)); + errorChain(uiMenuDraw( + &UI_SETTINGS.menu, UIFRAME_BORDER_WIDTH, - UIFRAME_BORDER_HEIGHT + (textCount * FONT_DEFAULT.tileset->tileHeight) + UIFRAME_BORDER_HEIGHT, + 300.0f - (UIFRAME_BORDER_WIDTH * 2), + 300.0f - (UIFRAME_BORDER_HEIGHT * 2) )); errorChain(spriteBatchFlush()); @@ -71,22 +75,15 @@ errorret_t uiSettingsDraw(void) { } bool_t uiSettingsIsOpen(void) { - return UI_SETTINGS.item != NULL; + return uiMenuIsActive(&UI_SETTINGS.menu); } void uiSettingsOpen() { - if(UI_SETTINGS.item != NULL) return; - - UI_SETTINGS.item = uiFocusPush( - 1, 4, uiSettingsSelected, uiSettingsChanged, uiSettingsClosed - ); + uiMenuOpen(&UI_SETTINGS.menu); } void uiSettingsClose() { - if(UI_SETTINGS.item == NULL) return; - - uiFocusPopItem(UI_SETTINGS.item); - UI_SETTINGS.item = NULL; + uiMenuClose(&UI_SETTINGS.menu); } errorret_t uiSettingsDispose(void) { diff --git a/src/dusk/ui/frame/uisettings.h b/src/dusk/ui/frame/uisettings.h index 7b2ef2f9..386dcbfd 100644 --- a/src/dusk/ui/frame/uisettings.h +++ b/src/dusk/ui/frame/uisettings.h @@ -7,12 +7,13 @@ #pragma once #include "error/error.h" -#include "ui/focus/uifocus.h" -#include "ui/widget/uicheckbox.h" +#include "ui/widget/uimenu.h" + +#define UI_SETTINGS_ITEM_COUNT 4 typedef struct { - uifocusitem_t *item; - uicheckbox_t checkboxTest; + uimenu_t menu; + uimenuitem_t items[UI_SETTINGS_ITEM_COUNT]; } uisettings_t; extern uisettings_t UI_SETTINGS; diff --git a/src/dusk/ui/widget/CMakeLists.txt b/src/dusk/ui/widget/CMakeLists.txt index 70f9d666..5c3c5f88 100644 --- a/src/dusk/ui/widget/CMakeLists.txt +++ b/src/dusk/ui/widget/CMakeLists.txt @@ -6,4 +6,5 @@ target_sources(${DUSK_LIBRARY_TARGET_NAME} PUBLIC uicheckbox.c + uimenu.c ) diff --git a/src/dusk/ui/widget/uimenu.c b/src/dusk/ui/widget/uimenu.c new file mode 100644 index 00000000..5e7b8a5a --- /dev/null +++ b/src/dusk/ui/widget/uimenu.c @@ -0,0 +1,163 @@ +/** + * Copyright (c) 2026 Dominic Masters + * + * This software is released under the MIT License. + * https://opensource.org/licenses/MIT + */ + +#include "uimenu.h" +#include "assert/assert.h" +#include "util/memory.h" +#include "display/text/text.h" +#include "display/color.h" + +void uiMenuInit( + uimenu_t *menu, + uimenuselectedcallback_t selected, + uimenuclosedcallback_t closed, + uimenuchangedcallback_t changed +) { + assertNotNull(menu, "Menu cannot be NULL"); + memoryZero(menu, sizeof(uimenu_t)); + menu->selected = selected; + menu->closed = closed; + menu->changed = changed; +} + +void uiMenuSetItems( + uimenu_t *menu, + const uimenuitem_t *items, + const uint8_t itemCount, + const uint8_t columns +) { + assertNotNull(menu, "Menu cannot be NULL"); + assertNotNull(items, "Items cannot be NULL"); + assertTrue(itemCount > 0, "Item count must be > 0"); + assertTrue(columns > 0, "Columns must be > 0"); + menu->items = (uimenuitem_t *)items; + menu->itemCount = itemCount; + menu->columns = columns; +} + +void uiMenuSetPosition(uimenu_t *menu, const uint8_t x, const uint8_t y) { + assertNotNull(menu, "Menu cannot be NULL"); + if(menu->focusItem == NULL) return; + uiFocusSetPosition(menu->focusItem, x, y); +} + +void uiMenuOpen(uimenu_t *menu) { + assertNotNull(menu, "Menu cannot be NULL"); + assertNotNull(menu->items, "Menu items cannot be NULL"); + assertTrue(menu->itemCount > 0, "Menu item count must be > 0"); + assertTrue(menu->columns > 0, "Menu columns must be > 0"); + if(menu->focusItem != NULL) return; + + uint8_t rows = (menu->itemCount + menu->columns - 1) / menu->columns; + menu->focusItem = uiFocusPush( + menu->columns, rows, + uiMenuFocusSelected, + uiMenuFocusChanged, + uiMenuFocusClosed, + menu + ); +} + +void uiMenuClose(uimenu_t *menu) { + assertNotNull(menu, "Menu cannot be NULL"); + if(menu->focusItem == NULL) return; + uiFocusPopItem(menu->focusItem); + menu->focusItem = NULL; +} + +bool_t uiMenuIsActive(const uimenu_t *menu) { + assertNotNull(menu, "Menu cannot be NULL"); + return menu->focusItem != NULL; +} + +errorret_t uiMenuDraw( + const uimenu_t *menu, + const float_t x, + const float_t y, + const float_t width, + const float_t height +) { + assertNotNull(menu, "Menu cannot be NULL"); + if(menu->itemCount == 0) errorOk(); + + float_t colStep = width / (float_t)menu->columns; + float_t rowHeight = (float_t)FONT_DEFAULT.tileset->tileHeight; + + uint8_t focusIndex = menu->focusItem != NULL ? + menu->focusItem->y * menu->columns + menu->focusItem->x : + 0xFF; + + for(uint8_t i = 0; i < menu->itemCount; i++) { + const uimenuitem_t *item = &menu->items[i]; + uint8_t col = i % menu->columns; + uint8_t row = i / menu->columns; + float_t ix = x + (float_t)col * colStep; + float_t iy = y + (float_t)row * rowHeight; + bool_t highlighted = i == focusIndex; + + switch(item->type) { + case UIMENU_WIDGET_TYPE_LABEL: + errorChain(textDraw( + ix, iy, item->label, + highlighted ? COLOR_RED : COLOR_WHITE, + &FONT_DEFAULT + )); + break; + + case UIMENU_WIDGET_TYPE_CHECKBOX: + errorChain(uiCheckboxDraw(&item->checkbox, ix, iy)); + break; + + default: + break; + } + } + + errorOk(); +} + +bool_t uiMenuFocusSelected(const uifocusitem_t *focusItem) { + assertNotNull(focusItem, "Focus item cannot be NULL"); + assertNotNull(focusItem->user, "Focus item user cannot be NULL"); + uimenu_t *menu = (uimenu_t *)focusItem->user; + if(menu->selected == NULL) return true; + + uint8_t index = focusItem->y * menu->columns + focusItem->x; + if(index >= menu->itemCount) return true; + + menu->selected(menu, index, &menu->items[index]); + return true; +} + +bool_t uiMenuFocusChanged(const uifocusitem_t *focusItem) { + assertNotNull(focusItem, "Focus item cannot be NULL"); + assertNotNull(focusItem->user, "Focus item user cannot be NULL"); + uimenu_t *menu = (uimenu_t *)focusItem->user; + + uint8_t index = focusItem->y * menu->columns + focusItem->x; + + for(uint8_t i = 0; i < menu->itemCount; i++) { + uimenuitem_t *item = &menu->items[i]; + if(item->type != UIMENU_WIDGET_TYPE_CHECKBOX) continue; + uiCheckboxSetHighlighted(&item->checkbox, i == index); + } + + if(menu->changed == NULL) return true; + if(index >= menu->itemCount) return true; + + menu->changed(menu, index, &menu->items[index]); + return true; +} + +bool_t uiMenuFocusClosed(const uifocusitem_t *focusItem) { + assertNotNull(focusItem, "Focus item cannot be NULL"); + assertNotNull(focusItem->user, "Focus item user cannot be NULL"); + uimenu_t *menu = (uimenu_t *)focusItem->user; + menu->focusItem = NULL; + if(menu->closed != NULL) menu->closed(menu); + return true; +} diff --git a/src/dusk/ui/widget/uimenu.h b/src/dusk/ui/widget/uimenu.h new file mode 100644 index 00000000..1acdf57d --- /dev/null +++ b/src/dusk/ui/widget/uimenu.h @@ -0,0 +1,160 @@ +/** + * Copyright (c) 2026 Dominic Masters + * + * This software is released under the MIT License. + * https://opensource.org/licenses/MIT + */ + +#pragma once +#include "error/error.h" +#include "ui/focus/uifocus.h" +#include "ui/widget/uicheckbox.h" + +#define UI_MENU_LABEL_MAX UI_CHECKBOX_LABEL_MAX + +typedef struct uimenu_s uimenu_t; + +typedef enum { + UIMENU_WIDGET_TYPE_NONE, + UIMENU_WIDGET_TYPE_LABEL, + UIMENU_WIDGET_TYPE_CHECKBOX, +} uimenuwidgettype_t; + +typedef struct { + uimenuwidgettype_t type; + union { + char_t label[UI_MENU_LABEL_MAX]; + uicheckbox_t checkbox; + }; +} uimenuitem_t; + +typedef void (*uimenuselectedcallback_t)( + const uimenu_t *menu, + const uint8_t index, + const uimenuitem_t *item +); + +typedef void (*uimenuchangedcallback_t)( + const uimenu_t *menu, + const uint8_t index, + const uimenuitem_t *item +); + +typedef void (*uimenuclosedcallback_t)(const uimenu_t *menu); + +typedef struct uimenu_s { + uimenuitem_t *items; + uint8_t itemCount; + uint8_t columns; + uifocusitem_t *focusItem; + + uimenuselectedcallback_t selected; + uimenuclosedcallback_t closed; + uimenuchangedcallback_t changed; +} uimenu_t; + +/** + * Initializes a menu, clearing all items and focus state. + * + * @param menu The menu to initialize. + * @param items The list of items to display in the menu. + * @param itemCount The number of items in the list. + * @param columns The number of columns to display the items in. + * @param selected The callback to invoke when an item is selected. + * @param closed The callback to invoke when the menu is closed. + * @param changed The callback to invoke when the menu changes. + */ +void uiMenuInit( + uimenu_t *menu, + uimenuselectedcallback_t selected, + uimenuclosedcallback_t closed, + uimenuchangedcallback_t changed +); + +/** + * Sets the items to display in the menu. + * + * @param menu The menu to update. + * @param items The list of items to display in the menu. + * @param itemCount The number of items in the list. + * @param columns The number of columns to display the items in. + */ +void uiMenuSetItems( + uimenu_t *menu, + const uimenuitem_t *items, + const uint8_t itemCount, + const uint8_t columns +); + +/** + * Sets the position of the menu on the screen. + * + * @param menu The menu to position. + * @param x The x-coordinate to position the menu at. + * @param y The y-coordinate to position the menu at. + */ +void uiMenuSetPosition(uimenu_t *menu, const uint8_t x, const uint8_t y); + +/** + * Pushes a menu onto the UI focus stack, making it the active menu. + * + * @param menu The menu to push. + */ +void uiMenuOpen(uimenu_t *menu); + +/** + * Pops a menu from the UI focus stack, removing it from the active menu. + * + * @param menu The menu to pop. + */ +void uiMenuClose(uimenu_t *menu); + +/** + * Returns whether the menu is currently active (on the UI focus stack). + * + * @param menu The menu to query. + * @returns True if the menu is active. + */ +bool_t uiMenuIsActive(const uimenu_t *menu); + +/** + * Draws the menu at the specified position and size. + * + * @param menu The menu to draw. + * @param x The x-coordinate to draw the menu at. + * @param y The y-coordinate to draw the menu at. + * @param width The width of the menu. + * @param height The height of the menu. + * @returns An error code indicating success or failure. + */ +errorret_t uiMenuDraw( + const uimenu_t *menu, + const float_t x, + const float_t y, + const float_t width, + const float_t height +); + +/** + * Internal focus callback — forwards selection to the menu's selected handler. + * + * @param focusItem The active focus item; user field must point to uimenu_t. + * @returns True. + */ +bool_t uiMenuFocusSelected(const uifocusitem_t *focusItem); + +/** + * Internal focus callback — updates checkbox highlights and fires changed. + * + * @param focusItem The active focus item; user field must point to uimenu_t. + * @returns True. + */ +bool_t uiMenuFocusChanged(const uifocusitem_t *focusItem); + +/** + * Internal focus callback — clears focusItem and fires the closed handler. + * + * @param focusItem The active focus item; user field must point to uimenu_t. + * @returns True. + */ +bool_t uiMenuFocusClosed(const uifocusitem_t *focusItem); \ No newline at end of file -- 2.52.0 From 722fe2ccfb16deedff184a69463bd495c5ea2bb6 Mon Sep 17 00:00:00 2001 From: Dominic Masters Date: Thu, 25 Jun 2026 19:56:14 -0500 Subject: [PATCH 09/16] Some label fixes --- CMakeLists.txt | 7 +- cmake/targets/dolphin.cmake | 2 +- src/dusk/ui/frame/uisettings.c | 43 +++++++--- src/dusk/ui/frame/uisettings.h | 2 +- src/dusk/ui/widget/CMakeLists.txt | 1 + src/dusk/ui/widget/uibutton.c | 44 ++++++++++ src/dusk/ui/widget/uibutton.h | 54 ++++++++++++ src/dusk/ui/widget/uicheckbox.c | 2 +- src/dusk/ui/widget/uicheckbox.h | 6 +- src/dusk/ui/widget/uimenu.c | 94 ++++++++++++++++----- src/dusk/ui/widget/uimenu.h | 36 ++++++-- test/assert/test_assert.c | 2 +- test/asset/test_asset.c | 20 ++--- test/asset/test_assetlocale.c | 16 ++-- test/thread/test_thread.c | 6 +- tools/makedolphiniso.py | 6 +- types/asset/assetbatch.d.ts | 8 +- types/asset/assetentry.d.ts | 14 +-- types/display/texture.d.ts | 2 +- types/entity/component/overworld.d.ts | 4 +- types/entity/component/overworldcamera.d.ts | 2 +- types/entity/component/physics.d.ts | 4 +- types/entity/component/position.d.ts | 2 +- types/entity/component/renderable.d.ts | 4 +- types/index.d.ts | 2 +- types/input/input.d.ts | 2 +- types/item/backpack.d.ts | 2 +- types/item/item.d.ts | 4 +- types/scene/scene.d.ts | 2 +- types/story/story.d.ts | 2 +- 30 files changed, 290 insertions(+), 105 deletions(-) create mode 100644 src/dusk/ui/widget/uibutton.c create mode 100644 src/dusk/ui/widget/uibutton.h diff --git a/CMakeLists.txt b/CMakeLists.txt index ed0a5731..f3a57347 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -14,10 +14,9 @@ cmake_policy(SET CMP0079 NEW) option(DUSK_BUILD_TESTS "Enable tests" OFF) -# Game identity — override these per-project -set(DUSK_GAME_NAME "Dusk" CACHE STRING "Game display name") -set(DUSK_GAME_AUTHOR "YouWish" CACHE STRING "Game author / coder") -set(DUSK_GAME_SHORT_DESCRIPTION "Dusk game" CACHE STRING "One-line description") +set(DUSK_GAME_NAME "Dusk" CACHE STRING "Game display name") +set(DUSK_GAME_AUTHOR "YourWishes" CACHE STRING "Game author / coder") +set(DUSK_GAME_SHORT_DESCRIPTION "Dusk game" CACHE STRING "One-line description") set(DUSK_GAME_LONG_DESCRIPTION "No description yet." CACHE STRING "Full description") # Prep cache diff --git a/cmake/targets/dolphin.cmake b/cmake/targets/dolphin.cmake index dac969e1..ff6164ba 100644 --- a/cmake/targets/dolphin.cmake +++ b/cmake/targets/dolphin.cmake @@ -45,7 +45,7 @@ endif() # Link libraries. # zip/z/lzma use target_link_options (raw flags) to bypass cmake target -# resolution — pkg-config-generated targets for these carry ZLIB::ZLIB in +# resolution - pkg-config-generated targets for these carry ZLIB::ZLIB in # INTERFACE_LINK_LIBRARIES which breaks the PPC link step. target_link_libraries(${DUSK_LIBRARY_TARGET_NAME} PRIVATE cglm diff --git a/src/dusk/ui/frame/uisettings.c b/src/dusk/ui/frame/uisettings.c index 551ada08..768ed5bf 100644 --- a/src/dusk/ui/frame/uisettings.c +++ b/src/dusk/ui/frame/uisettings.c @@ -18,7 +18,7 @@ void uiSettingsSelected( const uint8_t index, const uimenuitem_t *item ) { - if(item->type == UIMENU_WIDGET_TYPE_CHECKBOX) { + if(item->type == UI_MENU_WIDGET_TYPE_CHECKBOX) { uiCheckboxToggle(&UI_SETTINGS.items[index].checkbox); } } @@ -30,27 +30,42 @@ void uiSettingsClosed(const uimenu_t *menu) { errorret_t uiSettingsInit(void) { memoryZero(&UI_SETTINGS, sizeof(uisettings_t)); - UI_SETTINGS.items[0] = (uimenuitem_t){ - .type = UIMENU_WIDGET_TYPE_LABEL, + uint8_t i = 0; + + UI_SETTINGS.items[i] = (uimenuitem_t){ + .type = UI_MENU_WIDGET_TYPE_LABEL, .label = "Settings" }; - UI_SETTINGS.items[1] = (uimenuitem_t){ - .type = UIMENU_WIDGET_TYPE_LABEL, - .label = "Settings two" + ++i; + + UI_SETTINGS.items[i].type = UI_MENU_WIDGET_TYPE_BUTTON; + uiButtonInit(&UI_SETTINGS.items[i].button, "Do thing"); + ++i; + + UI_SETTINGS.items[i].type = UI_MENU_WIDGET_TYPE_BUTTON; + uiButtonInit(&UI_SETTINGS.items[i].button, "Do other thing"); + ++i; + + UI_SETTINGS.items[i].type = UI_MENU_WIDGET_TYPE_SPACER; + ++i; + + UI_SETTINGS.items[i] = (uimenuitem_t){ + .type = UI_MENU_WIDGET_TYPE_LABEL, + .label = "Options" }; - UI_SETTINGS.items[2] = (uimenuitem_t){ - .type = UIMENU_WIDGET_TYPE_LABEL, - .label = "Settings three" - }; - UI_SETTINGS.items[3].type = UIMENU_WIDGET_TYPE_CHECKBOX; - uiCheckboxInit(&UI_SETTINGS.items[3].checkbox, "Enable thing?"); + ++i; + + UI_SETTINGS.items[i].type = UI_MENU_WIDGET_TYPE_CHECKBOX; + uiCheckboxInit(&UI_SETTINGS.items[i].checkbox, "Enable thing?"); + ++i; + + assertTrue(i <= UI_SETTINGS_ITEM_COUNT, "Settings item count mismatch"); uiMenuInit(&UI_SETTINGS.menu, uiSettingsSelected, uiSettingsClosed, NULL); uiMenuSetItems( &UI_SETTINGS.menu, UI_SETTINGS.items, - UI_SETTINGS_ITEM_COUNT, - 2 + i, 1 ); errorOk(); diff --git a/src/dusk/ui/frame/uisettings.h b/src/dusk/ui/frame/uisettings.h index 386dcbfd..928bb4ed 100644 --- a/src/dusk/ui/frame/uisettings.h +++ b/src/dusk/ui/frame/uisettings.h @@ -9,7 +9,7 @@ #include "error/error.h" #include "ui/widget/uimenu.h" -#define UI_SETTINGS_ITEM_COUNT 4 +#define UI_SETTINGS_ITEM_COUNT 6 typedef struct { uimenu_t menu; diff --git a/src/dusk/ui/widget/CMakeLists.txt b/src/dusk/ui/widget/CMakeLists.txt index 5c3c5f88..028e726e 100644 --- a/src/dusk/ui/widget/CMakeLists.txt +++ b/src/dusk/ui/widget/CMakeLists.txt @@ -5,6 +5,7 @@ target_sources(${DUSK_LIBRARY_TARGET_NAME} PUBLIC + uibutton.c uicheckbox.c uimenu.c ) diff --git a/src/dusk/ui/widget/uibutton.c b/src/dusk/ui/widget/uibutton.c new file mode 100644 index 00000000..912f2946 --- /dev/null +++ b/src/dusk/ui/widget/uibutton.c @@ -0,0 +1,44 @@ +/** + * Copyright (c) 2026 Dominic Masters + * + * This software is released under the MIT License. + * https://opensource.org/licenses/MIT + */ + +#include "uibutton.h" +#include "assert/assert.h" +#include "util/memory.h" +#include "util/string.h" +#include "display/text/text.h" +#include "display/color.h" + +void uiButtonInit(uibutton_t *button, const char_t *label) { + assertNotNull(button, "Button cannot be NULL"); + assertNotNull(label, "Label cannot be NULL"); + memoryZero(button, sizeof(uibutton_t)); + button->label = label; +} + +bool_t uiButtonIsHighlighted(const uibutton_t *button) { + assertNotNull(button, "Button cannot be NULL"); + return button->highlighted; +} + +void uiButtonSetHighlighted(uibutton_t *button, const bool_t highlighted) { + assertNotNull(button, "Button cannot be NULL"); + button->highlighted = highlighted; +} + +errorret_t uiButtonDraw( + const uibutton_t *button, + const float_t x, + const float_t y +) { + assertNotNull(button, "Button cannot be NULL"); + errorChain(textDraw( + x, y, button->label, + button->highlighted ? COLOR_RED : COLOR_WHITE, + &FONT_DEFAULT + )); + errorOk(); +} diff --git a/src/dusk/ui/widget/uibutton.h b/src/dusk/ui/widget/uibutton.h new file mode 100644 index 00000000..b8a28268 --- /dev/null +++ b/src/dusk/ui/widget/uibutton.h @@ -0,0 +1,54 @@ +/** + * Copyright (c) 2026 Dominic Masters + * + * This software is released under the MIT License. + * https://opensource.org/licenses/MIT + */ + +#pragma once +#include "error/error.h" + +#define UI_BUTTON_LABEL_MAX 32 + +typedef struct { + const char_t *label; + bool_t highlighted; +} uibutton_t; + +/** + * Initializes a button. + * + * @param button The button to initialize. + * @param label Display label. + */ +void uiButtonInit(uibutton_t *button, const char_t *label); + +/** + * Returns whether the button is highlighted. + * + * @param button The button to query. + * @returns True if highlighted. + */ +bool_t uiButtonIsHighlighted(const uibutton_t *button); + +/** + * Sets the highlighted state of the button. + * + * @param button The button to update. + * @param highlighted The new highlighted state. + */ +void uiButtonSetHighlighted(uibutton_t *button, const bool_t highlighted); + +/** + * Draws the button at the given screen position. + * + * @param button The button to draw. + * @param x Screen x position. + * @param y Screen y position. + * @return Any error that occurs. + */ +errorret_t uiButtonDraw( + const uibutton_t *button, + const float_t x, + const float_t y +); diff --git a/src/dusk/ui/widget/uicheckbox.c b/src/dusk/ui/widget/uicheckbox.c index 813cce1a..afbf34dc 100644 --- a/src/dusk/ui/widget/uicheckbox.c +++ b/src/dusk/ui/widget/uicheckbox.c @@ -16,7 +16,7 @@ void uiCheckboxInit( const char_t *label ) { memoryZero(checkbox, sizeof(uicheckbox_t)); - stringCopy(checkbox->label, label, UI_CHECKBOX_LABEL_MAX); + checkbox->label = label; } bool_t uiCheckboxIsChecked(const uicheckbox_t *checkbox) { diff --git a/src/dusk/ui/widget/uicheckbox.h b/src/dusk/ui/widget/uicheckbox.h index 2d06b619..b32d52cc 100644 --- a/src/dusk/ui/widget/uicheckbox.h +++ b/src/dusk/ui/widget/uicheckbox.h @@ -8,10 +8,8 @@ #pragma once #include "error/error.h" -#define UI_CHECKBOX_LABEL_MAX 32 - typedef struct uicheckbox_s { - char_t label[UI_CHECKBOX_LABEL_MAX]; + const char_t *label; bool_t checked; bool_t highlighted; } uicheckbox_t; @@ -20,7 +18,7 @@ typedef struct uicheckbox_s { * Initializes a checkbox. * * @param checkbox The checkbox to initialize. - * @param label Display label, truncated to UI_CHECKBOX_LABEL_MAX - 1 chars. + * @param label Display label. */ void uiCheckboxInit( uicheckbox_t *checkbox, diff --git a/src/dusk/ui/widget/uimenu.c b/src/dusk/ui/widget/uimenu.c index 5e7b8a5a..5ff2e4b7 100644 --- a/src/dusk/ui/widget/uimenu.c +++ b/src/dusk/ui/widget/uimenu.c @@ -10,6 +10,7 @@ #include "util/memory.h" #include "display/text/text.h" #include "display/color.h" +#include "ui/widget/uibutton.h" void uiMenuInit( uimenu_t *menu, @@ -52,7 +53,9 @@ void uiMenuOpen(uimenu_t *menu) { assertTrue(menu->columns > 0, "Menu columns must be > 0"); if(menu->focusItem != NULL) return; - uint8_t rows = (menu->itemCount + menu->columns - 1) / menu->columns; + uint8_t focusable = uiMenuFocusableCount(menu); + uint8_t rows = focusable > 0 ? + (focusable + menu->columns - 1) / menu->columns : 1; menu->focusItem = uiFocusPush( menu->columns, rows, uiMenuFocusSelected, @@ -87,34 +90,49 @@ errorret_t uiMenuDraw( float_t colStep = width / (float_t)menu->columns; float_t rowHeight = (float_t)FONT_DEFAULT.tileset->tileHeight; - uint8_t focusIndex = menu->focusItem != NULL ? - menu->focusItem->y * menu->columns + menu->focusItem->x : - 0xFF; + uint8_t col = 0; + uint8_t row = 0; for(uint8_t i = 0; i < menu->itemCount; i++) { const uimenuitem_t *item = &menu->items[i]; - uint8_t col = i % menu->columns; - uint8_t row = i / menu->columns; + + if(item->type == UI_MENU_WIDGET_TYPE_LABEL) { + if(col > 0) { row++; col = 0; } + errorChain(textDraw( + x, + y + (float_t)row * rowHeight, + item->label, + COLOR_WHITE, + &FONT_DEFAULT + )); + row++; + continue; + } + + if(item->type == UI_MENU_WIDGET_TYPE_SPACER) { + if(col > 0) { row++; col = 0; } + row++; + continue; + } + float_t ix = x + (float_t)col * colStep; float_t iy = y + (float_t)row * rowHeight; - bool_t highlighted = i == focusIndex; switch(item->type) { - case UIMENU_WIDGET_TYPE_LABEL: - errorChain(textDraw( - ix, iy, item->label, - highlighted ? COLOR_RED : COLOR_WHITE, - &FONT_DEFAULT - )); + case UI_MENU_WIDGET_TYPE_CHECKBOX: + errorChain(uiCheckboxDraw(&item->checkbox, ix, iy)); break; - case UIMENU_WIDGET_TYPE_CHECKBOX: - errorChain(uiCheckboxDraw(&item->checkbox, ix, iy)); + case UI_MENU_WIDGET_TYPE_BUTTON: + errorChain(uiButtonDraw(&item->button, ix, iy)); break; default: break; } + + col++; + if(col >= menu->columns) { col = 0; row++; } } errorOk(); @@ -126,8 +144,9 @@ bool_t uiMenuFocusSelected(const uifocusitem_t *focusItem) { uimenu_t *menu = (uimenu_t *)focusItem->user; if(menu->selected == NULL) return true; - uint8_t index = focusItem->y * menu->columns + focusItem->x; - if(index >= menu->itemCount) return true; + uint8_t slot = focusItem->y * menu->columns + focusItem->x; + uint8_t index = uiMenuFocusSlotToIndex(menu, slot); + if(index == 0xFF) return true; menu->selected(menu, index, &menu->items[index]); return true; @@ -138,16 +157,27 @@ bool_t uiMenuFocusChanged(const uifocusitem_t *focusItem) { assertNotNull(focusItem->user, "Focus item user cannot be NULL"); uimenu_t *menu = (uimenu_t *)focusItem->user; - uint8_t index = focusItem->y * menu->columns + focusItem->x; + uint8_t focusSlot = focusItem->y * menu->columns + focusItem->x; + uint8_t slot = 0; for(uint8_t i = 0; i < menu->itemCount; i++) { uimenuitem_t *item = &menu->items[i]; - if(item->type != UIMENU_WIDGET_TYPE_CHECKBOX) continue; - uiCheckboxSetHighlighted(&item->checkbox, i == index); + if( + item->type == UI_MENU_WIDGET_TYPE_LABEL || + item->type == UI_MENU_WIDGET_TYPE_SPACER + ) continue; + bool_t isActive = (slot == focusSlot); + if(item->type == UI_MENU_WIDGET_TYPE_CHECKBOX) { + uiCheckboxSetHighlighted(&item->checkbox, isActive); + } else if(item->type == UI_MENU_WIDGET_TYPE_BUTTON) { + uiButtonSetHighlighted(&item->button, isActive); + } + slot++; } if(menu->changed == NULL) return true; - if(index >= menu->itemCount) return true; + uint8_t index = uiMenuFocusSlotToIndex(menu, focusSlot); + if(index == 0xFF) return true; menu->changed(menu, index, &menu->items[index]); return true; @@ -161,3 +191,25 @@ bool_t uiMenuFocusClosed(const uifocusitem_t *focusItem) { if(menu->closed != NULL) menu->closed(menu); return true; } + +uint8_t uiMenuFocusableCount(const uimenu_t *menu) { + assertNotNull(menu, "Menu cannot be NULL"); + uint8_t count = 0; + for(uint8_t i = 0; i < menu->itemCount; i++) { + uimenuwidgettype_t t = menu->items[i].type; + if(t != UI_MENU_WIDGET_TYPE_LABEL && t != UI_MENU_WIDGET_TYPE_SPACER) count++; + } + return count; +} + +uint8_t uiMenuFocusSlotToIndex(const uimenu_t *menu, const uint8_t slot) { + assertNotNull(menu, "Menu cannot be NULL"); + uint8_t focusable = 0; + for(uint8_t i = 0; i < menu->itemCount; i++) { + uimenuwidgettype_t t = menu->items[i].type; + if(t == UI_MENU_WIDGET_TYPE_LABEL || t == UI_MENU_WIDGET_TYPE_SPACER) continue; + if(focusable == slot) return i; + focusable++; + } + return 0xFF; +} diff --git a/src/dusk/ui/widget/uimenu.h b/src/dusk/ui/widget/uimenu.h index 1acdf57d..0f8e0301 100644 --- a/src/dusk/ui/widget/uimenu.h +++ b/src/dusk/ui/widget/uimenu.h @@ -8,6 +8,7 @@ #pragma once #include "error/error.h" #include "ui/focus/uifocus.h" +#include "ui/widget/uibutton.h" #include "ui/widget/uicheckbox.h" #define UI_MENU_LABEL_MAX UI_CHECKBOX_LABEL_MAX @@ -15,16 +16,19 @@ typedef struct uimenu_s uimenu_t; typedef enum { - UIMENU_WIDGET_TYPE_NONE, - UIMENU_WIDGET_TYPE_LABEL, - UIMENU_WIDGET_TYPE_CHECKBOX, + UI_MENU_WIDGET_TYPE_NONE, + UI_MENU_WIDGET_TYPE_LABEL, + UI_MENU_WIDGET_TYPE_SPACER, + UI_MENU_WIDGET_TYPE_CHECKBOX, + UI_MENU_WIDGET_TYPE_BUTTON, } uimenuwidgettype_t; typedef struct { uimenuwidgettype_t type; union { - char_t label[UI_MENU_LABEL_MAX]; + char_t *label; uicheckbox_t checkbox; + uibutton_t button; }; } uimenuitem_t; @@ -136,7 +140,25 @@ errorret_t uiMenuDraw( ); /** - * Internal focus callback — forwards selection to the menu's selected handler. + * Returns the number of focusable (non-label) items in the menu. + * + * @param menu The menu to query. + * @returns Count of non-label items. + */ +uint8_t uiMenuFocusableCount(const uimenu_t *menu); + +/** + * Maps a flat focus slot index to the corresponding item array index, + * skipping over label items which are not focusable. + * + * @param menu The menu to query. + * @param slot The focus slot index (y * columns + x). + * @returns The item array index, or 0xFF if out of range. + */ +uint8_t uiMenuFocusSlotToIndex(const uimenu_t *menu, const uint8_t slot); + +/** + * Internal focus callback - forwards selection to the menu's selected handler. * * @param focusItem The active focus item; user field must point to uimenu_t. * @returns True. @@ -144,7 +166,7 @@ errorret_t uiMenuDraw( bool_t uiMenuFocusSelected(const uifocusitem_t *focusItem); /** - * Internal focus callback — updates checkbox highlights and fires changed. + * Internal focus callback - updates widget highlights and fires changed. * * @param focusItem The active focus item; user field must point to uimenu_t. * @returns True. @@ -152,7 +174,7 @@ bool_t uiMenuFocusSelected(const uifocusitem_t *focusItem); bool_t uiMenuFocusChanged(const uifocusitem_t *focusItem); /** - * Internal focus callback — clears focusItem and fires the closed handler. + * Internal focus callback - clears focusItem and fires the closed handler. * * @param focusItem The active focus item; user field must point to uimenu_t. * @returns True. diff --git a/test/assert/test_assert.c b/test/assert/test_assert.c index e65a1f5e..d79dd120 100644 --- a/test/assert/test_assert.c +++ b/test/assert/test_assert.c @@ -108,7 +108,7 @@ static void test_assertStrLenMin(void **state) { } static void test_assertIsMainThread(void **state) { - // The group setup recorded this thread as main — assertIsMainThread must pass. + // The group setup recorded this thread as main - assertIsMainThread must pass. assertIsMainThread("test thread is main, should not assert"); // assertNotMainThread must fail when called from the main thread. diff --git a/test/asset/test_asset.c b/test/asset/test_asset.c index a64c3197..0ff4d9ef 100644 --- a/test/asset/test_asset.c +++ b/test/asset/test_asset.c @@ -40,7 +40,7 @@ static int asset_setup(void **state) { // Save real callbacks so we can restore them in teardown. memoryCopy(saved_callbacks, ASSET_LOADER_CALLBACKS, sizeof(saved_callbacks)); - // Manually init ASSET — no thread, no ZIP. + // Manually init ASSET - no thread, no ZIP. memoryZero(&ASSET, sizeof(ASSET)); for(size_t i = 0; i < ASSET_LOADING_COUNT_MAX; i++) { threadMutexInit(&ASSET.loading[i].mutex); @@ -117,7 +117,7 @@ static void test_getEntry_distinct_names(void **state) { } // ============================================================ -// assetUpdate — state machine tests +// assetUpdate - state machine tests // ============================================================ static void test_update_entry_reaches_loaded(void **state) { @@ -252,7 +252,7 @@ static void test_update_overflow_queues_entries(void **state) { } assert_int_equal(entries[ASSET_LOADING_COUNT_MAX]->state, ASSET_ENTRY_STATE_NOT_STARTED); - // Unlock first batch — the reaper can collect them in update 2. + // Unlock first batch - the reaper can collect them in update 2. for(int i = 0; i < ASSET_LOADING_COUNT_MAX; i++) { assetEntryUnlock(entries[i]); } @@ -284,7 +284,7 @@ static void test_update_error_slot_stays_occupied(void **state) { assetUpdate(); assert_int_equal(entry->state, ASSET_ENTRY_STATE_ERROR); - // Unlike LOADED, the ERROR case does NOT clear the slot — it throws instead. + // Unlike LOADED, the ERROR case does NOT clear the slot - it throws instead. assert_true(loading_slot_has_entry(entry)); errorret_t ret = assetUpdate(); @@ -295,7 +295,7 @@ static void test_update_error_slot_stays_occupied(void **state) { } // ============================================================ -// assetEntryInit — input copy +// assetEntryInit - input copy // ============================================================ static void test_getEntry_null_input_stays_null(void **state) { @@ -312,7 +312,7 @@ static void test_getEntry_input_copied_into_entry(void **state) { assetentry_t *entry = assetGetEntry("test.texture", ASSET_LOADER_TYPE_TEXTURE, &input); - // input must have been copied — entry->input must point inside the entry. + // input must have been copied - entry->input must point inside the entry. assert_non_null(entry->input); assert_ptr_equal(entry->input, &entry->inputData); assert_int_equal((int)entry->input->texture, 42); @@ -321,7 +321,7 @@ static void test_getEntry_input_copied_into_entry(void **state) { } // ============================================================ -// assetUpdate — re-entrant sync loader +// assetUpdate - re-entrant sync loader // ============================================================ static assetentry_t *reentrant_inner_entry = NULL; @@ -362,7 +362,7 @@ static void test_update_reentrant_sync_loader(void **state) { } // ============================================================ -// assetGetEntry — dedup against non-NOT_STARTED entries +// assetGetEntry - dedup against non-NOT_STARTED entries // ============================================================ static void test_getEntry_returns_loaded_entry(void **state) { @@ -464,7 +464,7 @@ static void test_requireLoaded_propagates_error(void **state) { assetentry_t *entry = assetGetEntry("fail.locale", ASSET_LOADER_TYPE_LOCALE, NULL); - // requireLoaded spins assetUpdate until LOADED — but the loader always fails, + // requireLoaded spins assetUpdate until LOADED - but the loader always fails, // so the second assetUpdate sees ERROR and throws, which errorChain propagates. errorret_t ret = assetRequireLoaded(entry); assert_true(errorIsNotOk(ret)); @@ -489,7 +489,7 @@ int main(void) { cmocka_unit_test_setup_teardown(test_getEntry_null_input_stays_null, asset_setup, asset_teardown), cmocka_unit_test_setup_teardown(test_getEntry_input_copied_into_entry, asset_setup, asset_teardown), - // assetUpdate — state machine + // assetUpdate - state machine cmocka_unit_test_setup_teardown(test_update_noop_on_empty_table, asset_setup, asset_teardown), cmocka_unit_test_setup_teardown(test_update_entry_reaches_loaded, asset_setup, asset_teardown), cmocka_unit_test_setup_teardown(test_update_slot_occupied_after_first_update, asset_setup, asset_teardown), diff --git a/test/asset/test_assetlocale.c b/test/asset/test_assetlocale.c index fd17d9fc..8a7d44d2 100644 --- a/test/asset/test_assetlocale.c +++ b/test/asset/test_assetlocale.c @@ -133,7 +133,7 @@ static int locale_teardown(void **state) { } // ============================================================ -// assetLocaleParseHeader — pure tests (no ZIP required) +// assetLocaleParseHeader - pure tests (no ZIP required) // ============================================================ static void test_parseHeader_english(void **state) { @@ -251,7 +251,7 @@ static void test_parseHeader_error_missing_nplurals(void **state) { } // ============================================================ -// assetLocaleEvaluatePlural — pure tests +// assetLocaleEvaluatePlural - pure tests // ============================================================ static void test_evaluatePlural_english_singular(void **state) { @@ -302,7 +302,7 @@ static void test_evaluatePlural_less_than_boundary(void **state) { } // ============================================================ -// assetLocaleGetString — ZIP-based tests +// assetLocaleGetString - ZIP-based tests // ============================================================ static void test_getString_simple(void **state) { @@ -355,7 +355,7 @@ static void test_getString_missing_id(void **state) { } // ============================================================ -// assetLocaleGetStringWithArgs — ZIP-based tests +// assetLocaleGetStringWithArgs - ZIP-based tests // ============================================================ static void test_getStringWithArgs_int(void **state) { @@ -392,7 +392,7 @@ static void test_getStringWithArgs_string(void **state) { int main(void) { const struct CMUnitTest tests[] = { - // parseHeader — pure + // parseHeader - pure cmocka_unit_test(test_parseHeader_english), cmocka_unit_test(test_parseHeader_singular), cmocka_unit_test(test_parseHeader_less_than), @@ -402,20 +402,20 @@ int main(void) { cmocka_unit_test(test_parseHeader_error_nplurals_too_large), cmocka_unit_test(test_parseHeader_error_missing_nplurals), - // evaluatePlural — pure + // evaluatePlural - pure cmocka_unit_test(test_evaluatePlural_english_singular), cmocka_unit_test(test_evaluatePlural_english_plural), cmocka_unit_test(test_evaluatePlural_singular_only), cmocka_unit_test(test_evaluatePlural_less_than_boundary), - // getString — in-memory ZIP + // getString - in-memory ZIP cmocka_unit_test_setup_teardown(test_getString_simple, locale_setup, locale_teardown), cmocka_unit_test_setup_teardown(test_getString_plural_singular, locale_setup, locale_teardown), cmocka_unit_test_setup_teardown(test_getString_plural_many, locale_setup, locale_teardown), cmocka_unit_test_setup_teardown(test_getString_multiple_calls, locale_setup, locale_teardown), cmocka_unit_test_setup_teardown(test_getString_missing_id, locale_setup, locale_teardown), - // getStringWithArgs — in-memory ZIP + // getStringWithArgs - in-memory ZIP cmocka_unit_test_setup_teardown(test_getStringWithArgs_int, locale_setup, locale_teardown), cmocka_unit_test_setup_teardown(test_getStringWithArgs_string, locale_setup, locale_teardown), }; diff --git a/test/thread/test_thread.c b/test/thread/test_thread.c index 039d128e..585ab348 100644 --- a/test/thread/test_thread.c +++ b/test/thread/test_thread.c @@ -45,7 +45,7 @@ static void test_thread_start_stop(void **state) { } static void test_thread_should_stop(void **state) { - // threadStop blocks until STOPPED — if threadShouldStop is broken the + // threadStop blocks until STOPPED - if threadShouldStop is broken the // looping callback never exits and this test hangs / times out. thread_t thread; threadInit(&thread, helper_loop); @@ -108,12 +108,12 @@ typedef struct { static void helper_trylock(thread_t *thread) { trylock_data_t *data = (trylock_data_t *)thread->data; - // Phase 1: main holds the lock — trylock must fail. + // Phase 1: main holds the lock - trylock must fail. while(data->phase != 1) {} data->resultWhileLocked = threadMutexTryLock(data->target); data->phase = 2; - // Phase 3: main released the lock — trylock must succeed. + // Phase 3: main released the lock - trylock must succeed. while(data->phase != 3) {} data->resultAfterUnlock = threadMutexTryLock(data->target); if(data->resultAfterUnlock) { diff --git a/tools/makedolphiniso.py b/tools/makedolphiniso.py index f1cd13ba..5456320e 100644 --- a/tools/makedolphiniso.py +++ b/tools/makedolphiniso.py @@ -4,7 +4,7 @@ Builds GameCube / Wii disc images (NTSC-J, NTSC-U, PAL) using xorrisofs / mkisofs + Swiss-GC system-area headers. The Swiss-GC headers contain a working GCN apploader that boots any DOL via -the El Torito catalog — no custom PPC assembly needed. +the El Torito catalog - no custom PPC assembly needed. Headers are cached in /../buildtools/iso/ and downloaded from the Swiss-GC repository on first use. @@ -14,8 +14,8 @@ Disc layout (produced by xorrisofs): sector 16 ISO 9660 PVD sector 17 El Torito Boot Record ... directory records, file data - Dusk.dol (El Torito boot image — loaded by apploader) - dusk.dsk (asset archive — found at runtime via ISO 9660) + Dusk.dol (El Torito boot image - loaded by apploader) + dusk.dsk (asset archive - found at runtime via ISO 9660) """ import argparse diff --git a/types/asset/assetbatch.d.ts b/types/asset/assetbatch.d.ts index 09b23a7b..64f5d474 100644 --- a/types/asset/assetbatch.d.ts +++ b/types/asset/assetbatch.d.ts @@ -8,11 +8,11 @@ /** * Descriptor for one entry in an `AssetBatch`. * - * `format` — texture format constant (`Texture.FORMAT_*`). Alias for `input` + * `format` - texture format constant (`Texture.FORMAT_*`). Alias for `input` * when the type is `Asset.TYPE_TEXTURE`. - * `axis` — mesh axis constant (`Asset.MESH_AXIS_*`). Alias for `input` + * `axis` - mesh axis constant (`Asset.MESH_AXIS_*`). Alias for `input` * when the type is `Asset.TYPE_MESH`. - * `input` — generic numeric input for all other loader types. + * `input` - generic numeric input for all other loader types. */ interface AssetBatchDescriptor { path: string; @@ -48,7 +48,7 @@ interface AssetBatch { lock(): this; /** * Releases all locks and clears the batch. - * After this call the object is invalid — do not use it again. + * After this call the object is invalid - do not use it again. */ unlock(): void; /** diff --git a/types/asset/assetentry.d.ts b/types/asset/assetentry.d.ts index f20b8d64..9feb9ee4 100644 --- a/types/asset/assetentry.d.ts +++ b/types/asset/assetentry.d.ts @@ -13,24 +13,24 @@ interface AssetEntry { /** Archive-relative path used as the cache key. */ readonly name: string; - /** Current loading state — compare against `AssetEntry.*` state constants. */ + /** Current loading state - compare against `AssetEntry.*` state constants. */ readonly state: number; - /** Loader type — one of the `AssetEntry.TYPE_*` constants. */ + /** Loader type - one of the `AssetEntry.TYPE_*` constants. */ readonly type: number; /** `true` when the entry has fully loaded (`state === AssetEntry.LOADED`). */ readonly isLoaded: boolean; /** * Returns a `Texture` for this entry when it is a loaded texture asset. - * The `Texture` holds its own asset lock — independent of this `AssetEntry`. + * The `Texture` holds its own asset lock - independent of this `AssetEntry`. * Returns `undefined` if the entry is not of type `Asset.TYPE_TEXTURE` or * is not yet loaded. */ readonly texture: Texture | undefined; - /** Event proxy — subscribe up to 4 callbacks for when loading completes. */ + /** Event proxy - subscribe up to 4 callbacks for when loading completes. */ readonly onLoaded: AssetEventProxy; - /** Event proxy — subscribe up to 4 callbacks for when the entry is disposed. */ + /** Event proxy - subscribe up to 4 callbacks for when the entry is disposed. */ readonly onUnloaded: AssetEventProxy; - /** Event proxy — subscribe up to 4 callbacks for when loading fails. */ + /** Event proxy - subscribe up to 4 callbacks for when loading fails. */ readonly onError: AssetEventProxy; /** * Returns a Promise that resolves when the entry is loaded, or rejects on @@ -45,7 +45,7 @@ interface AssetEntry { requireLoaded(): this; /** * Releases the lock immediately. - * After this call the object is invalid — do not use it again. + * After this call the object is invalid - do not use it again. */ unlock(): void; toString(): string; diff --git a/types/display/texture.d.ts b/types/display/texture.d.ts index 665ec7ad..904e7d11 100644 --- a/types/display/texture.d.ts +++ b/types/display/texture.d.ts @@ -6,7 +6,7 @@ */ /** - * A loaded texture asset. Holds a lock on the underlying asset entry — + * A loaded texture asset. Holds a lock on the underlying asset entry - * the lock is released automatically when the object is garbage collected. */ interface Texture { diff --git a/types/entity/component/overworld.d.ts b/types/entity/component/overworld.d.ts index 91696332..461c5bd6 100644 --- a/types/entity/component/overworld.d.ts +++ b/types/entity/component/overworld.d.ts @@ -8,11 +8,11 @@ /** Overworld character component (player or NPC). */ interface Overworld extends Component { /** - * Entity type — `Overworld.PLAYER` or `Overworld.NPC`. + * Entity type - `Overworld.PLAYER` or `Overworld.NPC`. */ type: number; /** - * Facing direction — one of the `Overworld.FACING_*` constants. + * Facing direction - one of the `Overworld.FACING_*` constants. */ facing: number; /** Component ID of the linked Renderable, or INVALID if none. */ diff --git a/types/entity/component/overworldcamera.d.ts b/types/entity/component/overworldcamera.d.ts index ecfa243f..5fcd1b5a 100644 --- a/types/entity/component/overworldcamera.d.ts +++ b/types/entity/component/overworldcamera.d.ts @@ -21,7 +21,7 @@ interface OverworldCamera extends Component { /** Orthographic scale factor (larger = wider view). */ scale: number; /** - * Convenience setter — equivalent to assigning `targetEntity` and + * Convenience setter - equivalent to assigning `targetEntity` and * `targetPositionComponent` individually. */ setTarget(targetEntityId: number, targetPositionComponentId: number): void; diff --git a/types/entity/component/physics.d.ts b/types/entity/component/physics.d.ts index c4616a24..abc31bcf 100644 --- a/types/entity/component/physics.d.ts +++ b/types/entity/component/physics.d.ts @@ -7,10 +7,10 @@ /** Physics body component. */ interface Physics extends Component { - /** Body simulation type — `Physics.STATIC`, `DYNAMIC`, or `KINEMATIC`. */ + /** Body simulation type - `Physics.STATIC`, `DYNAMIC`, or `KINEMATIC`. */ bodyType: number; /** - * Collision shape type — one of the `Physics.SHAPE_*` constants. + * Collision shape type - one of the `Physics.SHAPE_*` constants. * Changing the type preserves existing velocity and ground state. */ shape: number; diff --git a/types/entity/component/position.d.ts b/types/entity/component/position.d.ts index 5f7addfe..5cceb852 100644 --- a/types/entity/component/position.d.ts +++ b/types/entity/component/position.d.ts @@ -5,7 +5,7 @@ * https://opensource.org/licenses/MIT */ -/** Transform component — local/world PRS with an optional parent hierarchy. */ +/** Transform component - local/world PRS with an optional parent hierarchy. */ interface Position extends Component { /** * Local-space position. Reading returns a Vec3 copy; assigning a Vec3 diff --git a/types/entity/component/renderable.d.ts b/types/entity/component/renderable.d.ts index 11a0d77a..c8a4ef5f 100644 --- a/types/entity/component/renderable.d.ts +++ b/types/entity/component/renderable.d.ts @@ -7,7 +7,7 @@ /** A Renderable component. Returned by `entity.add(Component.RENDERABLE)`. */ interface Renderable extends Component { - /** Current render type — one of the `Renderable.*` type constants. */ + /** Current render type - one of the `Renderable.*` type constants. */ type: number; /** Render priority. 0 = auto. Higher = drawn later. */ priority: number; @@ -28,7 +28,7 @@ interface Renderable extends Component { texture: Texture | undefined; /** * Sprite list. Reading returns a JS array of 10-element sub-arrays - * `[x1,y1,z1, x2,y2,z2, u1,v1, u2,v2]` — one per sprite. + * `[x1,y1,z1, x2,y2,z2, u1,v1, u2,v2]` - one per sprite. * * Assigning an array replaces all sprites. Each element may be: * - 10 numbers (3D): `[x1,y1,z1, x2,y2,z2, u1,v1, u2,v2]` diff --git a/types/index.d.ts b/types/index.d.ts index e2d0edf4..6a9d3298 100644 --- a/types/index.d.ts +++ b/types/index.d.ts @@ -5,7 +5,7 @@ * https://opensource.org/licenses/MIT * * Root type declarations for the Dusk engine built-in JavaScript modules. - * These globals are injected by the native JerryScript runtime — they are not + * These globals are injected by the native JerryScript runtime - they are not * ES modules and cannot be imported. * * Reference this file from a jsconfig.json or tsconfig.json to get diff --git a/types/input/input.d.ts b/types/input/input.d.ts index e39a2f24..da26313b 100644 --- a/types/input/input.d.ts +++ b/types/input/input.d.ts @@ -37,7 +37,7 @@ interface InputNamespace { /** Polling-based input system. */ declare var Input: InputNamespace; -// Input action constants — injected as globals by the engine at startup. +// Input action constants - injected as globals by the engine at startup. declare var INPUT_ACTION_UP: InputAction; declare var INPUT_ACTION_DOWN: InputAction; declare var INPUT_ACTION_LEFT: InputAction; diff --git a/types/item/backpack.d.ts b/types/item/backpack.d.ts index d4eb9fd1..692f243d 100644 --- a/types/item/backpack.d.ts +++ b/types/item/backpack.d.ts @@ -58,6 +58,6 @@ interface BackpackNamespace { declare var Backpack: BackpackNamespace; -// Inventory sort constants — injected as globals by the engine at startup. +// Inventory sort constants - injected as globals by the engine at startup. declare var INVENTORY_SORT_BY_ID: number; declare var INVENTORY_SORT_BY_TYPE: number; diff --git a/types/item/item.d.ts b/types/item/item.d.ts index 00566669..021fb3b9 100644 --- a/types/item/item.d.ts +++ b/types/item/item.d.ts @@ -29,11 +29,11 @@ interface ItemNamespace { declare var Item: ItemNamespace; -// Item ID constants — injected as globals by the engine at startup. +// Item ID constants - injected as globals by the engine at startup. declare var ITEM_ID_POTION: ItemId; declare var ITEM_ID_POTATO: ItemId; declare var ITEM_ID_APPLE: ItemId; -// Item type constants — injected as globals by the engine at startup. +// Item type constants - injected as globals by the engine at startup. declare var ITEM_TYPE_MEDICINE: ItemType; declare var ITEM_TYPE_FOOD: ItemType; diff --git a/types/scene/scene.d.ts b/types/scene/scene.d.ts index b64e77ed..db3ee6db 100644 --- a/types/scene/scene.d.ts +++ b/types/scene/scene.d.ts @@ -43,7 +43,7 @@ interface SceneNamespace { */ set(newScene: SceneObject | null): void; - /** Called each frame by the engine — drives `current.update()`. */ + /** Called each frame by the engine - drives `current.update()`. */ update(): void; /** Called each frame by the engine for dynamic/physics updates. */ diff --git a/types/story/story.d.ts b/types/story/story.d.ts index 9b555afe..15745386 100644 --- a/types/story/story.d.ts +++ b/types/story/story.d.ts @@ -26,5 +26,5 @@ interface StoryNamespace { declare var Story: StoryNamespace; -// Story flag constants — injected as globals by the engine at startup. +// Story flag constants - injected as globals by the engine at startup. declare var STORY_FLAG_TEST: StoryFlag; -- 2.52.0 From 3f35d56be4b2b9072e81918341e8b7c967ea08fe Mon Sep 17 00:00:00 2001 From: Dominic Masters Date: Thu, 25 Jun 2026 20:58:44 -0500 Subject: [PATCH 10/16] UI Frame finally doing things --- src/dusk/engine/engine.c | 2 +- src/dusk/rpg/entity/entityinteract.c | 4 +- src/dusk/ui/CMakeLists.txt | 1 + src/dusk/ui/focus/uifocus.c | 36 ++-- src/dusk/ui/focus/uifocus.h | 1 + src/dusk/ui/frame/uiframe.c | 30 +-- src/dusk/ui/frame/uiframe.h | 12 +- src/dusk/ui/frame/uisettings.c | 8 +- src/dusk/ui/rpg/CMakeLists.txt | 10 + src/dusk/ui/rpg/uitextbox.c | 242 +++++++++++++++++++++++ src/dusk/ui/rpg/uitextbox.h | 127 ++++++++++++ src/dusk/ui/rpg/uitextboxmain.c | 85 ++++++++ src/dusk/ui/rpg/uitextboxmain.h | 86 +++++++++ src/dusk/ui/uielement.c | 16 +- src/dusk/ui/uitextbox.c | 278 --------------------------- src/dusk/ui/uitextbox.h | 121 ------------ 16 files changed, 608 insertions(+), 451 deletions(-) create mode 100644 src/dusk/ui/rpg/CMakeLists.txt create mode 100644 src/dusk/ui/rpg/uitextbox.c create mode 100644 src/dusk/ui/rpg/uitextbox.h create mode 100644 src/dusk/ui/rpg/uitextboxmain.c create mode 100644 src/dusk/ui/rpg/uitextboxmain.h delete mode 100644 src/dusk/ui/uitextbox.c delete mode 100644 src/dusk/ui/uitextbox.h diff --git a/src/dusk/engine/engine.c b/src/dusk/engine/engine.c index 9e110efb..04c234ad 100644 --- a/src/dusk/engine/engine.c +++ b/src/dusk/engine/engine.c @@ -61,10 +61,10 @@ errorret_t engineUpdate(void) { inputUpdate(); consoleUpdate(); errorChain(rpgUpdate()); - errorChain(uiUpdate()); errorChain(cutsceneUpdate()); errorChain(sceneUpdate()); errorChain(assetUpdate()); + errorChain(uiUpdate()); // Render errorChain(displayUpdate()); diff --git a/src/dusk/rpg/entity/entityinteract.c b/src/dusk/rpg/entity/entityinteract.c index d3b3ca7f..c618ab0c 100644 --- a/src/dusk/rpg/entity/entityinteract.c +++ b/src/dusk/rpg/entity/entityinteract.c @@ -7,8 +7,8 @@ #include "entity.h" #include "assert/assert.h" -#include "console/console.h" #include "rpg/cutscene/cutscenesystem.h" +#include "ui/rpg/uitextboxmain.h" void entityInteractWith(entity_t *player, entity_t *target) { assertNotNull(player, "Player entity pointer cannot be NULL"); @@ -24,7 +24,7 @@ void entityInteractWith(entity_t *player, entity_t *target) { return; case ENTITY_INTERACT_PRINT: - consolePrint(target->interact.data.message); + uiTextboxMainSetText(target->interact.data.message); return; case ENTITY_INTERACT_NULL: diff --git a/src/dusk/ui/CMakeLists.txt b/src/dusk/ui/CMakeLists.txt index 0c3b196e..d6a0d06f 100644 --- a/src/dusk/ui/CMakeLists.txt +++ b/src/dusk/ui/CMakeLists.txt @@ -7,6 +7,7 @@ add_subdirectory(debug) add_subdirectory(frame) add_subdirectory(focus) add_subdirectory(overlay) +add_subdirectory(rpg) add_subdirectory(widget) # Sources diff --git a/src/dusk/ui/focus/uifocus.c b/src/dusk/ui/focus/uifocus.c index 1b6103e8..49d86349 100644 --- a/src/dusk/ui/focus/uifocus.c +++ b/src/dusk/ui/focus/uifocus.c @@ -49,13 +49,14 @@ uifocusitem_t * uiFocusPush( item->closed = closed; item->user = user; UI_FOCUS.count++; + UI_FOCUS.pushedThisTick = true; if(item->changed != NULL) item->changed(item); return item; } void uiFocusPop(void) { assertTrue(UI_FOCUS.count > 0, "UI focus stack underflow"); - + uifocusitem_t *item = &UI_FOCUS.items[UI_FOCUS.count - 1]; if(item->closed != NULL) item->closed(item); UI_FOCUS.count--; @@ -120,36 +121,39 @@ void uiFocusMoveDirection( void uiFocusUpdate(void) { if(UI_FOCUS.count == 0) return; - // Current item + #ifdef DUSK_TIME_DYNAMIC + if(TIME.dynamicUpdate) return; + #endif + + if(UI_FOCUS.pushedThisTick) { + UI_FOCUS.pushedThisTick = false; + return; + } + uifocusitem_t *item = &UI_FOCUS.items[UI_FOCUS.count - 1]; - // Accept - if(inputPressedIfDynamic(INPUT_ACTION_ACCEPT)) { + if(inputPressed(INPUT_ACTION_ACCEPT)) { if(item->selected != NULL) item->selected(item); return; } - // Return - if(inputPressedIfDynamic(INPUT_ACTION_CANCEL)) { + if(inputPressed(INPUT_ACTION_CANCEL)) { uiFocusPop(); return; } - // Handle pressed directions for( const uifocusdirmap_t *m = UI_FOCUS_DIR_MAP; m->action != INPUT_ACTION_NULL; m++ ) { - if(!inputPressedIfDynamic(m->action)) continue; - + if(!inputPressed(m->action)) continue; UI_FOCUS.direction = m->direction; UI_FOCUS.timeHeld = 0.0f; uiFocusMoveDirection(item, m->direction); return; } - // Handle held directions bool_t held = false; for( const uifocusdirmap_t *m = UI_FOCUS_DIR_MAP; @@ -157,26 +161,18 @@ void uiFocusUpdate(void) { m++ ) { if(m->direction != UI_FOCUS.direction) continue; - - held = inputDownIfDynamic(m->action); + held = inputIsDown(m->action); break; } - // Are we still holding? if(!held) { UI_FOCUS.direction = UI_FOCUS_DIRECTION_NONE; UI_FOCUS.timeHeld = 0.0f; return; } + UI_FOCUS.timeHeld += TIME.delta; - #ifdef DUSK_TIME_DYNAMIC - UI_FOCUS.timeHeld += TIME.dynamicDelta; - #else - UI_FOCUS.timeHeld += TIME.delta; - #endif - - // Can tick? if(UI_FOCUS.timeHeld < UI_FOCUS_HOLD_DELAY) return; if(UI_FOCUS.timeHeld < UI_FOCUS_HOLD_DELAY + UI_FOCUS_HOLD_REPEAT) return; UI_FOCUS.timeHeld = UI_FOCUS_HOLD_DELAY; diff --git a/src/dusk/ui/focus/uifocus.h b/src/dusk/ui/focus/uifocus.h index 9369e097..09379831 100644 --- a/src/dusk/ui/focus/uifocus.h +++ b/src/dusk/ui/focus/uifocus.h @@ -53,6 +53,7 @@ typedef struct { uint8_t count; uifocusdirection_t direction; float_t timeHeld; + bool_t pushedThisTick; } uifocus_t; extern uifocus_t UI_FOCUS; diff --git a/src/dusk/ui/frame/uiframe.c b/src/dusk/ui/frame/uiframe.c index 3097fcbe..0406265a 100644 --- a/src/dusk/ui/frame/uiframe.c +++ b/src/dusk/ui/frame/uiframe.c @@ -22,33 +22,39 @@ errorret_t uiFrameInit(void) { color_t border = color4b(0, 100, 220, 255); color_t center = color4b(0, 100, 220, 200); - for(uint8_t y = 0; y < UIFRAME_BORDER_HEIGHT; y++) { - for(uint8_t x = 0; x < UIFRAME_BORDER_WIDTH; x++) { + for(uint8_t y = 0; y < UI_FRAME_TEXTURE_HEIGHT_POW2; y++) { + for(uint8_t x = 0; x < UI_FRAME_TEXTURE_WIDTH_POW2; x++) { color_t c; - if(x >= 12 || y >= 12) {// Because power of two is required. + if(x >= UI_FRAME_TEXTURE_WIDTH || y >= UI_FRAME_TEXTURE_HEIGHT) { c = COLOR_TRANSPARENT; - } else if(y < 4 || y >= 8 || x < 4 || x >= 8) { + } else if( + y < UI_FRAME_TILE_HEIGHT || y >= UI_FRAME_TILE_HEIGHT * 2 || + x < UI_FRAME_TILE_WIDTH || x >= UI_FRAME_TILE_WIDTH * 2 + ) { c = border; } else { c = center; } - UI_FRAME.pixels[y * UIFRAME_BORDER_WIDTH + x] = c; + UI_FRAME.pixels[y * UI_FRAME_TEXTURE_WIDTH_POW2 + x] = c; } } errorChain(textureInit( - &UI_FRAME.texture, UIFRAME_BORDER_WIDTH, UIFRAME_BORDER_HEIGHT, + &UI_FRAME.texture, + UI_FRAME_TEXTURE_WIDTH_POW2, UI_FRAME_TEXTURE_HEIGHT_POW2, TEXTURE_FORMAT_RGBA, (texturedata_t){ .rgbaColors = UI_FRAME.pixels } )); - UI_FRAME.tileset.tileWidth = 4; - UI_FRAME.tileset.tileHeight = 4; + UI_FRAME.tileset.tileWidth = UI_FRAME_TILE_WIDTH; + UI_FRAME.tileset.tileHeight = UI_FRAME_TILE_HEIGHT; UI_FRAME.tileset.columns = 3; UI_FRAME.tileset.rows = 3; UI_FRAME.tileset.tileCount = 9; - UI_FRAME.tileset.uv[0] = 4.0f / UIFRAME_BORDER_WIDTH; - UI_FRAME.tileset.uv[1] = 4.0f / UIFRAME_BORDER_HEIGHT; + UI_FRAME.tileset.uv[0] = + (float_t)UI_FRAME_TILE_WIDTH / UI_FRAME_TEXTURE_WIDTH_POW2; + UI_FRAME.tileset.uv[1] = + (float_t)UI_FRAME_TILE_HEIGHT / UI_FRAME_TEXTURE_HEIGHT_POW2; errorOk(); } @@ -66,8 +72,8 @@ errorret_t uiFrameDraw( } }; spritebatchsprite_t sprites[9]; - float_t tileW = (float_t)UI_FRAME.tileset.tileWidth; - float_t tileH = (float_t)UI_FRAME.tileset.tileHeight; + float_t tileW = (float_t)UI_FRAME_BORDER_WIDTH; + float_t tileH = (float_t)UI_FRAME_BORDER_HEIGHT; sprites[0] = spriteBatchSpriteTilesetPosition( &UI_FRAME.tileset, 0, 0, diff --git a/src/dusk/ui/frame/uiframe.h b/src/dusk/ui/frame/uiframe.h index 20511a2e..9fef7257 100644 --- a/src/dusk/ui/frame/uiframe.h +++ b/src/dusk/ui/frame/uiframe.h @@ -10,13 +10,19 @@ #include "display/texture/texture.h" #include "display/texture/tileset.h" -#define UIFRAME_BORDER_WIDTH 16 -#define UIFRAME_BORDER_HEIGHT 16 +#define UI_FRAME_BORDER_WIDTH 6 +#define UI_FRAME_BORDER_HEIGHT 6 +#define UI_FRAME_TILE_WIDTH 1 +#define UI_FRAME_TILE_HEIGHT 1 +#define UI_FRAME_TEXTURE_WIDTH (UI_FRAME_TILE_WIDTH * 3) +#define UI_FRAME_TEXTURE_HEIGHT (UI_FRAME_TILE_HEIGHT * 3) +#define UI_FRAME_TEXTURE_WIDTH_POW2 4 +#define UI_FRAME_TEXTURE_HEIGHT_POW2 4 typedef struct { tileset_t tileset; texture_t texture; - color_t pixels[UIFRAME_BORDER_WIDTH * UIFRAME_BORDER_HEIGHT]; + color_t pixels[UI_FRAME_BORDER_WIDTH * UI_FRAME_BORDER_HEIGHT]; } uiframe_t; extern uiframe_t UI_FRAME; diff --git a/src/dusk/ui/frame/uisettings.c b/src/dusk/ui/frame/uisettings.c index 768ed5bf..14ff6cd6 100644 --- a/src/dusk/ui/frame/uisettings.c +++ b/src/dusk/ui/frame/uisettings.c @@ -79,10 +79,10 @@ errorret_t uiSettingsDraw(void) { errorChain(uiFrameDraw(0.0f, 0.0f, 300.0f, 300)); errorChain(uiMenuDraw( &UI_SETTINGS.menu, - UIFRAME_BORDER_WIDTH, - UIFRAME_BORDER_HEIGHT, - 300.0f - (UIFRAME_BORDER_WIDTH * 2), - 300.0f - (UIFRAME_BORDER_HEIGHT * 2) + UI_FRAME_BORDER_WIDTH, + UI_FRAME_BORDER_HEIGHT, + 300.0f - (UI_FRAME_BORDER_WIDTH * 2), + 300.0f - (UI_FRAME_BORDER_HEIGHT * 2) )); errorChain(spriteBatchFlush()); diff --git a/src/dusk/ui/rpg/CMakeLists.txt b/src/dusk/ui/rpg/CMakeLists.txt new file mode 100644 index 00000000..e248e4fe --- /dev/null +++ b/src/dusk/ui/rpg/CMakeLists.txt @@ -0,0 +1,10 @@ +# Copyright (c) 2026 Dominic Masters +# +# This software is released under the MIT License. +# https://opensource.org/licenses/MIT + +target_sources(${DUSK_LIBRARY_TARGET_NAME} + PUBLIC + uitextbox.c + uitextboxmain.c +) diff --git a/src/dusk/ui/rpg/uitextbox.c b/src/dusk/ui/rpg/uitextbox.c new file mode 100644 index 00000000..a08636fe --- /dev/null +++ b/src/dusk/ui/rpg/uitextbox.c @@ -0,0 +1,242 @@ +/** + * Copyright (c) 2026 Dominic Masters + * + * This software is released under the MIT License. + * https://opensource.org/licenses/MIT + */ + +#include "uitextbox.h" +#include "assert/assert.h" +#include "util/memory.h" +#include "util/string.h" +#include "time/time.h" +#include "display/text/text.h" +#include "display/color.h" +#include "display/spritebatch/spritebatch.h" +#include "display/shader/shaderunlit.h" +#include "ui/frame/uiframe.h" + +void uiTextboxInit(uitextbox_t *box) { + assertNotNull(box, "Textbox cannot be NULL"); + memoryZero(box, sizeof(uitextbox_t)); +} + +void uiTextboxSetText(uitextbox_t *box, const char_t *text) { + assertNotNull(box, "Textbox cannot be NULL"); + assertNotNull(text, "Text cannot be NULL"); + stringCopy(box->text, text, UI_TEXTBOX_TEXT_MAX); + box->currentPage = 0; + box->scroll = 0; + box->layoutWidth = 0.0f; + box->layoutHeight = 0.0f; +} + +void uiTextboxBuildLayout( + uitextbox_t *box, + const float_t width, + const float_t height +) { + assertNotNull(box, "Textbox cannot be NULL"); + + box->layoutWidth = width; + box->layoutHeight = height; + box->lineCount = 0; + box->pageCount = 1; + + float_t fontW = (float_t)FONT_DEFAULT.tileset->tileWidth; + float_t fontH = (float_t)FONT_DEFAULT.tileset->tileHeight; + + if(fontW <= 0.0f || fontH <= 0.0f) return; + + box->charsPerLine = (int32_t)(width / fontW); + box->linesPerPage = (int32_t)(height / (fontH + UI_TEXTBOX_LINE_SPACING)); + if(box->linesPerPage > UI_TEXTBOX_LINES_PER_PAGE_MAX) { + box->linesPerPage = UI_TEXTBOX_LINES_PER_PAGE_MAX; + } + + if(box->charsPerLine <= 0 || box->linesPerPage <= 0) return; + if(box->text[0] == '\0') return; + + char_t *src = box->text; + int32_t i = 0; + + while(src[i] != '\0' && box->lineCount < UI_TEXTBOX_LINES_MAX) { + if(src[i] == '\t') { + i++; + int32_t rem = box->lineCount % box->linesPerPage; + int32_t pad = rem > 0 ? box->linesPerPage - rem : 0; + while(pad > 0 && box->lineCount < UI_TEXTBOX_LINES_MAX) { + box->lines[box->lineCount].start = i; + box->lines[box->lineCount].count = 0; + box->lineCount++; + pad--; + } + continue; + } + + int32_t lineStart = i; + int32_t lineWidth = 0; + + while(src[i] != '\0') { + char_t c = src[i]; + + if(c == '\n') { i++; break; } + if(c == '\t') break; + + if(c == ' ') { + int32_t wordLen = 0; + int32_t j = i + 1; + while( + src[j] != ' ' && src[j] != '\n' && + src[j] != '\t' && src[j] != '\0' + ) { + wordLen++; + j++; + } + + if(lineWidth > 0 && lineWidth + 1 + wordLen > box->charsPerLine) { + i++; + break; + } + + lineWidth++; + i++; + } else { + if(lineWidth >= box->charsPerLine) break; + lineWidth++; + i++; + } + } + + box->lines[box->lineCount].start = lineStart; + box->lines[box->lineCount].count = lineWidth; + box->lineCount++; + } + + if(box->lineCount == 0) { + box->pageCount = 1; + } else { + box->pageCount = + (box->lineCount + box->linesPerPage - 1) / box->linesPerPage; + } +} + +errorret_t uiTextboxUpdate(uitextbox_t *box) { + assertNotNull(box, "Textbox cannot be NULL"); + +#ifdef DUSK_TIME_DYNAMIC + if(TIME.dynamicUpdate) errorOk(); +#endif + + if(!uiTextboxPageIsComplete(box)) { + box->scroll += UI_TEXTBOX_SCROLL_CHARS_PER_TICK; + } + + errorOk(); +} + +errorret_t uiTextboxDraw( + uitextbox_t *box, + const float_t x, + const float_t y, + const float_t width, + const float_t height +) { + assertNotNull(box, "Textbox cannot be NULL"); + + float_t borderW = (float_t)UI_FRAME_BORDER_WIDTH; + float_t borderH = (float_t)UI_FRAME_BORDER_HEIGHT; + float_t contentX = x + borderW; + float_t contentY = y + borderH; + float_t contentW = width - 2.0f * borderW; + float_t contentH = height - 2.0f * borderH; + + if(contentW != box->layoutWidth || contentH != box->layoutHeight) { + uiTextboxBuildLayout(box, contentW, contentH); + } + + errorChain(uiFrameDraw(x, y, width, height)); + errorChain(spriteBatchFlush()); + + if(box->lineCount == 0 || box->text[0] == '\0') errorOk(); + + float_t fontW = (float_t)FONT_DEFAULT.tileset->tileWidth; + float_t fontH = (float_t)FONT_DEFAULT.tileset->tileHeight; + + shadermaterial_t material = { + .unlit = { + .color = COLOR_WHITE, + .texture = FONT_DEFAULT.texture + } + }; + + int32_t pageFirst = box->currentPage * box->linesPerPage; + int32_t pageLast = pageFirst + box->linesPerPage; + if(pageLast > box->lineCount) pageLast = box->lineCount; + + int32_t charsLeft = box->scroll; + + for(int32_t li = pageFirst; li < pageLast && charsLeft > 0; li++) { + uitextboxline_t *line = &box->lines[li]; + int32_t visible = line->count < charsLeft ? line->count : charsLeft; + float_t lineY = contentY + + (float_t)(li - pageFirst) * (fontH + UI_TEXTBOX_LINE_SPACING); + + for(int32_t ci = 0; ci < visible; ci++) { + char_t c = box->text[line->start + ci]; + if(c == ' ') continue; + spritebatchsprite_t sprite = textGetSprite( + (vec2){ contentX + (float_t)ci * fontW, lineY }, + c, + &FONT_DEFAULT + ); + errorChain(spriteBatchBuffer(&sprite, 1, &SHADER_UNLIT, material)); + } + + charsLeft -= visible; + } + + if(uiTextboxPageIsComplete(box)) { + spritebatchsprite_t caret = textGetSprite( + (vec2){ + contentX + contentW - fontW, + contentY + contentH - fontH + }, + 'v', + &FONT_DEFAULT + ); + errorChain(spriteBatchBuffer(&caret, 1, &SHADER_UNLIT, material)); + } + + errorChain(spriteBatchFlush()); + errorOk(); +} + +int32_t uiTextboxGetPageCharCount(const uitextbox_t *box) { + assertNotNull(box, "Textbox cannot be NULL"); + int32_t first = box->currentPage * box->linesPerPage; + int32_t last = first + box->linesPerPage; + if(last > box->lineCount) last = box->lineCount; + int32_t total = 0; + for(int32_t i = first; i < last; i++) { + total += box->lines[i].count; + } + return total; +} + +bool_t uiTextboxPageIsComplete(const uitextbox_t *box) { + assertNotNull(box, "Textbox cannot be NULL"); + return box->scroll >= uiTextboxGetPageCharCount(box); +} + +bool_t uiTextboxHasNextPage(const uitextbox_t *box) { + assertNotNull(box, "Textbox cannot be NULL"); + return box->currentPage + 1 < box->pageCount; +} + +void uiTextboxNextPage(uitextbox_t *box) { + assertNotNull(box, "Textbox cannot be NULL"); + if(!uiTextboxHasNextPage(box)) return; + box->currentPage++; + box->scroll = 0; +} diff --git a/src/dusk/ui/rpg/uitextbox.h b/src/dusk/ui/rpg/uitextbox.h new file mode 100644 index 00000000..2132a764 --- /dev/null +++ b/src/dusk/ui/rpg/uitextbox.h @@ -0,0 +1,127 @@ +/** + * Copyright (c) 2026 Dominic Masters + * + * This software is released under the MIT License. + * https://opensource.org/licenses/MIT + */ + +#pragma once +#include "error/error.h" + +#define UI_TEXTBOX_TEXT_MAX 1024 +#define UI_TEXTBOX_LINES_MAX 64 +#define UI_TEXTBOX_LINES_PER_PAGE_MAX 4 +#define UI_TEXTBOX_SCROLL_CHARS_PER_TICK 1 +#define UI_TEXTBOX_LINE_SPACING 0.0f + +typedef struct { + int32_t start; + int32_t count; +} uitextboxline_t; + +typedef struct { + char_t text[UI_TEXTBOX_TEXT_MAX]; + + uitextboxline_t lines[UI_TEXTBOX_LINES_MAX]; + int32_t lineCount; + int32_t charsPerLine; + int32_t linesPerPage; + int32_t pageCount; + + // last dimensions used for layout; rebuild triggers when these change + float_t layoutWidth; + float_t layoutHeight; + + int32_t currentPage; + int32_t scroll; +} uitextbox_t; + +/** + * Initializes a textbox, zeroing all state. + * + * @param box The textbox to initialize. + */ +void uiTextboxInit(uitextbox_t *box); + +/** + * Copies text into the textbox and marks layout as dirty. + * Resets currentPage and scroll to 0. + * + * @param box The textbox to update. + * @param text Null-terminated source string. + */ +void uiTextboxSetText(uitextbox_t *box, const char_t *text); + +/** + * Rebuilds word-wrap and page layout for the given draw dimensions. + * Called automatically by uiTextboxDraw when width or height changes. + * + * @param box The textbox to rebuild. + * @param width Available content width in pixels. + * @param height Available content height in pixels. + */ +void uiTextboxBuildLayout( + uitextbox_t *box, + const float_t width, + const float_t height +); + +/** + * Advances the typewriter scroll by UI_TEXTBOX_SCROLL_CHARS_PER_TICK. + * Skipped on dynamic ticks. + * + * @param box The textbox to update. + * @returns Any error that occurs. + */ +errorret_t uiTextboxUpdate(uitextbox_t *box); + +/** + * Draws the textbox frame and visible text. Rebuilds layout automatically + * if width or height differs from the last draw call. + * + * @param box The textbox to draw. + * @param x Screen x position. + * @param y Screen y position. + * @param width Draw width in pixels. + * @param height Draw height in pixels. + * @returns Any error that occurs. + */ +errorret_t uiTextboxDraw( + uitextbox_t *box, + const float_t x, + const float_t y, + const float_t width, + const float_t height +); + +/** + * Returns the total visible char count for the current page. + * + * @param box The textbox to query. + * @returns Total chars on the current page. + */ +int32_t uiTextboxGetPageCharCount(const uitextbox_t *box); + +/** + * Returns true when scroll has fully revealed the current page. + * + * @param box The textbox to query. + * @returns True if the current page is fully visible. + */ +bool_t uiTextboxPageIsComplete(const uitextbox_t *box); + +/** + * Returns true when there is at least one more page after the current one. + * + * @param box The textbox to query. + * @returns True if a next page exists. + */ +bool_t uiTextboxHasNextPage(const uitextbox_t *box); + +/** + * Advances to the next page and resets scroll to 0. + * Has no effect if already on the last page. + * + * @param box The textbox to advance. + */ +void uiTextboxNextPage(uitextbox_t *box); diff --git a/src/dusk/ui/rpg/uitextboxmain.c b/src/dusk/ui/rpg/uitextboxmain.c new file mode 100644 index 00000000..263f2d85 --- /dev/null +++ b/src/dusk/ui/rpg/uitextboxmain.c @@ -0,0 +1,85 @@ +/** + * Copyright (c) 2026 Dominic Masters + * + * This software is released under the MIT License. + * https://opensource.org/licenses/MIT + */ + +#include "uitextboxmain.h" +#include "ui/focus/uifocus.h" +#include "display/screen/screen.h" +#include "display/text/text.h" +#include "ui/frame/uiframe.h" + +uitextbox_t UI_TEXTBOX_MAIN; +static uifocusitem_t *focusItem = NULL; + +errorret_t uiTextboxMainInit(void) { + uiTextboxInit(&UI_TEXTBOX_MAIN); + errorOk(); +} + +void uiTextboxMainSetText(const char_t *text) { + uiTextboxSetText(&UI_TEXTBOX_MAIN, text); + if(focusItem != NULL) return; + focusItem = uiFocusPush( + 1, 1, + uiTextboxMainFocusSelected, + NULL, + uiTextboxMainFocusClosed, + NULL + ); +} + +errorret_t uiTextboxMainUpdate(void) { + if(focusItem == NULL) errorOk(); + return uiTextboxUpdate(&UI_TEXTBOX_MAIN); +} + +errorret_t uiTextboxMainDraw(void) { + if(focusItem == NULL) errorOk(); + float_t fontH = (float_t)FONT_DEFAULT.tileset->tileHeight; + float_t h = (float_t)UI_TEXTBOX_MAIN_LINES * fontH + + (float_t)(UI_TEXTBOX_MAIN_LINES - 1) * UI_TEXTBOX_LINE_SPACING + + 2.0f * (float_t)UI_FRAME_BORDER_HEIGHT; + float_t w = (float_t)SCREEN.scanWidth; + float_t x = (float_t)SCREEN.scanX; + float_t y = (float_t)(SCREEN.scanY + SCREEN.scanHeight) - h; + return uiTextboxDraw(&UI_TEXTBOX_MAIN, x, y, w, h); +} + +bool_t uiTextboxMainPageIsComplete(void) { + return uiTextboxPageIsComplete(&UI_TEXTBOX_MAIN); +} + +bool_t uiTextboxMainHasNextPage(void) { + return uiTextboxHasNextPage(&UI_TEXTBOX_MAIN); +} + +void uiTextboxMainNextPage(void) { + uiTextboxNextPage(&UI_TEXTBOX_MAIN); +} + +bool_t uiTextboxMainIsActive(void) { + return focusItem != NULL; +} + +bool_t uiTextboxMainFocusSelected(const uifocusitem_t *item) { + if(!uiTextboxPageIsComplete(&UI_TEXTBOX_MAIN)) { + UI_TEXTBOX_MAIN.scroll = uiTextboxGetPageCharCount(&UI_TEXTBOX_MAIN); + return true; + } + + if(uiTextboxHasNextPage(&UI_TEXTBOX_MAIN)) { + uiTextboxNextPage(&UI_TEXTBOX_MAIN); + return true; + } + + uiFocusPopItem(focusItem); + return true; +} + +bool_t uiTextboxMainFocusClosed(const uifocusitem_t *item) { + focusItem = NULL; + return true; +} diff --git a/src/dusk/ui/rpg/uitextboxmain.h b/src/dusk/ui/rpg/uitextboxmain.h new file mode 100644 index 00000000..04284ab0 --- /dev/null +++ b/src/dusk/ui/rpg/uitextboxmain.h @@ -0,0 +1,86 @@ +/** + * Copyright (c) 2026 Dominic Masters + * + * This software is released under the MIT License. + * https://opensource.org/licenses/MIT + */ + +#pragma once +#include "ui/rpg/uitextbox.h" +#include "ui/focus/uifocusitem.h" + +#define UI_TEXTBOX_MAIN_LINES 4 + +extern uitextbox_t UI_TEXTBOX_MAIN; + +/** + * Initializes UI_TEXTBOX_MAIN. + * + * @returns Any error that occurs. + */ +errorret_t uiTextboxMainInit(void); + +/** + * Copies text into UI_TEXTBOX_MAIN and resets page and scroll. + * + * @param text Null-terminated source string. + */ +void uiTextboxMainSetText(const char_t *text); + +/** + * Advances the typewriter scroll for UI_TEXTBOX_MAIN. + * + * @returns Any error that occurs. + */ +errorret_t uiTextboxMainUpdate(void); + +/** + * Draws UI_TEXTBOX_MAIN full-width at the bottom of the screen. + * Position and size are derived from SCREEN each call. + * + * @returns Any error that occurs. + */ +errorret_t uiTextboxMainDraw(void); + +/** + * Returns true when the current page is fully scrolled in. + * + * @returns True if the current page is complete. + */ +bool_t uiTextboxMainPageIsComplete(void); + +/** + * Returns true when at least one more page follows the current one. + * + * @returns True if a next page exists. + */ +bool_t uiTextboxMainHasNextPage(void); + +/** + * Advances UI_TEXTBOX_MAIN to the next page and resets scroll. + * Has no effect if already on the last page. + */ +void uiTextboxMainNextPage(void); + +/** + * Returns true when UI_TEXTBOX_MAIN has focus (is visible and active). + * + * @returns True if the textbox is currently active. + */ +bool_t uiTextboxMainIsActive(void); + +/** + * Internal focus callback - skip scroll or advance page or dismiss. + * + * @param item The active focus item. + * @returns True. + */ +bool_t uiTextboxMainFocusSelected(const uifocusitem_t *item); + +/** + * Internal focus callback - clears the focus item pointer on dismiss. + * + * @param item The focus item being closed. + * @returns True. + */ +bool_t uiTextboxMainFocusClosed(const uifocusitem_t *item); diff --git a/src/dusk/ui/uielement.c b/src/dusk/ui/uielement.c index 577bcf9e..5c73e0bd 100644 --- a/src/dusk/ui/uielement.c +++ b/src/dusk/ui/uielement.c @@ -10,14 +10,13 @@ #include "ui/frame/uiframe.h" #include "ui/debug/uifps.h" #include "engine/engine.h" -#include "ui/uitextbox.h" #include "ui/overlay/uifullbox.h" #include "ui/overlay/uiloading.h" #include "ui/debug/uiplayerpos.h" #include "ui/overlay/uicrop.h" #include "ui/debug/uiconsole.h" -#include "ui/frame/uiframe.h" #include "ui/frame/uisettings.h" +#include "ui/rpg/uitextboxmain.h" uielement_t UI_ELEMENTS[] = { { @@ -39,14 +38,11 @@ uielement_t UI_ELEMENTS[] = { .dispose = uiSettingsDispose }, - // { .type = UI_ELEMENT_TYPE_SCRIPT, .script = { .script = "ui/test.js" } }, - - // { - // .init = uiTextboxInit, - // .update = uiTextboxUpdate, - // .draw = uiTextboxDraw, - // .dispose = uiTextboxDispose - // }, + { + .init = uiTextboxMainInit, + .update = uiTextboxMainUpdate, + .draw = uiTextboxMainDraw + }, // Fullbox over: above absolutely everything. { diff --git a/src/dusk/ui/uitextbox.c b/src/dusk/ui/uitextbox.c deleted file mode 100644 index fc1231f2..00000000 --- a/src/dusk/ui/uitextbox.c +++ /dev/null @@ -1,278 +0,0 @@ -// // Copyright (c) 2026 Dominic Masters -// // -// // This software is released under the MIT License. -// // https://opensource.org/licenses/MIT - -// #include "uitextbox.h" -// #include "assert/assert.h" -// #include "util/memory.h" -// #include "util/string.h" -// #include "time/time.h" -// #include "input/input.h" -// #include "event/event.h" -// #include "display/screen/screen.h" -// #include "display/texture/texture.h" -// #include "display/text/text.h" -// #include "display/spritebatch/spritebatch.h" -// #include "display/shader/shaderunlit.h" - -// uitextbox_t UI_TEXTBOX; - -// errorret_t uiTextboxInit(void) { -// memoryZero(&UI_TEXTBOX, sizeof(uitextbox_t)); -// UI_TEXTBOX.textColor = COLOR_WHITE; -// UI_TEXTBOX.advanceAction = INPUT_ACTION_ACCEPT; - -// UI_TEXTBOX.font = &FONT_DEFAULT; - -// float_t fontW = (float_t)FONT_DEFAULT.tileset->tileWidth; -// float_t fontH = (float_t)FONT_DEFAULT.tileset->tileHeight; -// float_t tbHeight = ( -// (float_t)UI_TEXTBOX_LINES_PER_PAGE_MAX * fontH + -// (float_t)(UI_TEXTBOX_LINES_PER_PAGE_MAX - 1) * UI_TEXTBOX_LINE_SPACING + -// 2.0f * fontH -// ); -// UI_TEXTBOX.x = (float_t)SCREEN.scanX + 10.0f; -// UI_TEXTBOX.y = (float_t)(SCREEN.scanY + SCREEN.scanHeight) - -// tbHeight - 10.0f; -// UI_TEXTBOX.width = (float_t)SCREEN.scanWidth - 20.0f; -// UI_TEXTBOX.height = tbHeight; - -// UI_TEXTBOX.frame.tileset.columns = 3; -// UI_TEXTBOX.frame.tileset.rows = 3; -// UI_TEXTBOX.frame.tileset.tileCount = 9; -// UI_TEXTBOX.frame.tileset.tileWidth = FONT_DEFAULT.tileset->tileWidth; -// UI_TEXTBOX.frame.tileset.tileHeight = FONT_DEFAULT.tileset->tileHeight; -// UI_TEXTBOX.frame.tileset.uv[0] = 1.0f / 3.0f; -// UI_TEXTBOX.frame.tileset.uv[1] = 1.0f / 3.0f; -// UI_TEXTBOX.frame.texture = &TEXTURE_WHITE; - -// eventInit( -// &UI_TEXTBOX.onPageComplete, -// UI_TEXTBOX.onPageCompleteCallbacks, -// UI_TEXTBOX.onPageCompleteUsers, -// 4 -// ); -// eventInit( -// &UI_TEXTBOX.onLastPage, -// UI_TEXTBOX.onLastPageCallbacks, -// UI_TEXTBOX.onLastPageUsers, -// 4 -// ); - -// errorOk(); -// } - -// void uiTextboxBuildLayout(void) { -// UI_TEXTBOX.lineCount = 0; -// UI_TEXTBOX.pageCount = 1; - -// float_t frameTileW = (float_t)UI_TEXTBOX.frame.tileset.tileWidth; -// float_t frameTileH = (float_t)UI_TEXTBOX.frame.tileset.tileHeight; -// float_t fontW = (float_t)UI_TEXTBOX.font->tileset->tileWidth; -// float_t fontH = (float_t)UI_TEXTBOX.font->tileset->tileHeight; - -// if(fontW <= 0.0f || fontH <= 0.0f) return; - -// float_t contentW = UI_TEXTBOX.width - 2.0f * frameTileW; -// float_t contentH = UI_TEXTBOX.height - 2.0f * frameTileH; - -// UI_TEXTBOX.charsPerLine = (int32_t)(contentW / fontW); -// UI_TEXTBOX.linesPerPage = UI_TEXTBOX_LINES_PER_PAGE_MAX; - -// if(UI_TEXTBOX.charsPerLine <= 0 || UI_TEXTBOX.linesPerPage <= 0) return; -// if(UI_TEXTBOX.text[0] == '\0') return; - -// char_t *src = UI_TEXTBOX.text; -// int32_t i = 0; - -// while(src[i] != '\0' && UI_TEXTBOX.lineCount < UI_TEXTBOX_LINES_MAX) { -// if(src[i] == '\t') { -// i++; -// int32_t rem = UI_TEXTBOX.lineCount % UI_TEXTBOX.linesPerPage; -// int32_t pad = rem > 0 ? UI_TEXTBOX.linesPerPage - rem : 0; -// while(pad > 0 && UI_TEXTBOX.lineCount < UI_TEXTBOX_LINES_MAX) { -// UI_TEXTBOX.lines[UI_TEXTBOX.lineCount].start = i; -// UI_TEXTBOX.lines[UI_TEXTBOX.lineCount].count = 0; -// UI_TEXTBOX.lineCount++; -// pad--; -// } -// continue; -// } - -// int32_t lineStart = i; -// int32_t lineWidth = 0; - -// while(src[i] != '\0') { -// char_t c = src[i]; - -// if(c == '\n') { -// i++; -// break; -// } - -// if(c == '\t') break; - -// if(c == ' ') { -// int32_t wordLen = 0; -// int32_t j = i + 1; -// while( -// src[j] != ' ' && src[j] != '\n' && -// src[j] != '\t' && src[j] != '\0' -// ) { -// wordLen++; -// j++; -// } - -// if( -// lineWidth > 0 && -// lineWidth + 1 + wordLen > UI_TEXTBOX.charsPerLine -// ) { -// i++; -// break; -// } - -// lineWidth++; -// i++; -// } else { -// if(lineWidth >= UI_TEXTBOX.charsPerLine) break; -// lineWidth++; -// i++; -// } -// } - -// UI_TEXTBOX.lines[UI_TEXTBOX.lineCount].start = lineStart; -// UI_TEXTBOX.lines[UI_TEXTBOX.lineCount].count = lineWidth; -// UI_TEXTBOX.lineCount++; -// } - -// if(UI_TEXTBOX.lineCount == 0) { -// UI_TEXTBOX.pageCount = 1; -// } else { -// int32_t div = UI_TEXTBOX.lineCount + UI_TEXTBOX.linesPerPage - 1; -// UI_TEXTBOX.pageCount = div / UI_TEXTBOX.linesPerPage; -// } -// } - -// void uiTextboxSetText(const char_t *text) { -// assertNotNull(text, "text must not be NULL"); - -// stringCopy(UI_TEXTBOX.text, text, UI_TEXTBOX_TEXT_MAX); -// UI_TEXTBOX.currentPage = 0; -// UI_TEXTBOX.scroll = 0; -// uiTextboxBuildLayout(); -// } - -// errorret_t uiTextboxUpdate(void) { -// #ifdef DUSK_TIME_DYNAMIC -// if(TIME.dynamicUpdate) errorOk(); -// #endif - -// bool_t wasComplete = uiTextboxPageIsComplete(); - -// if(!wasComplete) { -// UI_TEXTBOX.scroll += UI_TEXTBOX_SCROLL_CHARS_PER_TICK; -// } - -// if(inputPressed(UI_TEXTBOX.advanceAction)) { -// if(!uiTextboxPageIsComplete()) { -// UI_TEXTBOX.scroll = uiTextboxGetPageCharCount(); -// } else if(uiTextboxHasNextPage()) { -// uiTextboxNextPage(); -// } -// } - -// if(!wasComplete && uiTextboxPageIsComplete()) { -// eventInvoke(&UI_TEXTBOX.onPageComplete, &UI_TEXTBOX); -// if(!uiTextboxHasNextPage()) { -// eventInvoke(&UI_TEXTBOX.onLastPage, &UI_TEXTBOX); -// } -// } - -// errorOk(); -// } - -// int32_t uiTextboxGetPageCharCount(void) { -// int32_t first = UI_TEXTBOX.currentPage * UI_TEXTBOX.linesPerPage; -// int32_t last = first + UI_TEXTBOX.linesPerPage; -// if(last > UI_TEXTBOX.lineCount) last = UI_TEXTBOX.lineCount; -// int32_t total = 0; -// for(int32_t i = first; i < last; i++) { -// total += UI_TEXTBOX.lines[i].count; -// } -// return total; -// } - -// bool_t uiTextboxPageIsComplete(void) { -// return UI_TEXTBOX.scroll >= uiTextboxGetPageCharCount(); -// } - -// bool_t uiTextboxHasNextPage(void) { -// return UI_TEXTBOX.currentPage + 1 < UI_TEXTBOX.pageCount; -// } - -// void uiTextboxNextPage(void) { -// if(!uiTextboxHasNextPage()) return; -// UI_TEXTBOX.currentPage++; -// UI_TEXTBOX.scroll = 0; -// } - -// errorret_t uiTextboxDraw(void) { -// if(UI_TEXTBOX.lineCount == 0 || UI_TEXTBOX.text[0] == '\0') errorOk(); - -// errorChain(uiFrameDraw( -// &UI_TEXTBOX.frame, -// UI_TEXTBOX.x, UI_TEXTBOX.y, -// UI_TEXTBOX.width, UI_TEXTBOX.height -// )); -// errorChain(spriteBatchFlush()); - -// shadermaterial_t textMaterial = { -// .unlit = { -// .color = UI_TEXTBOX.textColor, -// .texture = UI_TEXTBOX.font->texture -// } -// }; - -// float_t frameTileW = (float_t)UI_TEXTBOX.frame.tileset.tileWidth; -// float_t frameTileH = (float_t)UI_TEXTBOX.frame.tileset.tileHeight; -// float_t fontW = (float_t)UI_TEXTBOX.font->tileset->tileWidth; -// float_t fontH = (float_t)UI_TEXTBOX.font->tileset->tileHeight; -// float_t contentX = UI_TEXTBOX.x + frameTileW; -// float_t contentY = UI_TEXTBOX.y + frameTileH; - -// int32_t pageFirst = UI_TEXTBOX.currentPage * UI_TEXTBOX.linesPerPage; -// int32_t pageLast = pageFirst + UI_TEXTBOX.linesPerPage; -// if(pageLast > UI_TEXTBOX.lineCount) pageLast = UI_TEXTBOX.lineCount; - -// int32_t charsLeft = UI_TEXTBOX.scroll; - -// for(int32_t li = pageFirst; li < pageLast && charsLeft > 0; li++) { -// uitextboxline_t *line = &UI_TEXTBOX.lines[li]; -// int32_t visible = line->count < charsLeft ? line->count : charsLeft; -// float_t lineY = contentY + -// (float_t)(li - pageFirst) * (fontH + UI_TEXTBOX_LINE_SPACING); - -// for(int32_t ci = 0; ci < visible; ci++) { -// char_t c = UI_TEXTBOX.text[line->start + ci]; -// if(c == ' ') continue; -// spritebatchsprite_t sprite = textGetSprite( -// (vec2){ contentX + (float_t)ci * fontW, lineY }, -// c, -// UI_TEXTBOX.font -// ); -// errorChain(spriteBatchBuffer(&sprite, 1, &SHADER_UNLIT, textMaterial)); -// } - -// charsLeft -= visible; -// } - -// errorChain(spriteBatchFlush()); -// errorOk(); -// } - -// errorret_t uiTextboxDispose(void) { -// uiFrameDispose(&UI_TEXTBOX.frame); -// UI_TEXTBOX.font = NULL; -// errorOk(); -// } diff --git a/src/dusk/ui/uitextbox.h b/src/dusk/ui/uitextbox.h deleted file mode 100644 index 29606c20..00000000 --- a/src/dusk/ui/uitextbox.h +++ /dev/null @@ -1,121 +0,0 @@ -// /** -// * Copyright (c) 2026 Dominic Masters -// * -// * This software is released under the MIT License. -// * https://opensource.org/licenses/MIT -// */ - -// #pragma once -// #include "ui/frame/uiframe.h" -// #include "display/text/text.h" -// #include "input/inputaction.h" -// #include "event/event.h" - -// #define UI_TEXTBOX_TEXT_MAX 1024 -// #define UI_TEXTBOX_LINES_MAX 64 -// #define UI_TEXTBOX_SCROLL_CHARS_PER_TICK 1 -// #define UI_TEXTBOX_LINES_PER_PAGE_MAX 3 -// #define UI_TEXTBOX_LINE_SPACING 0.0f - -// typedef struct { -// int32_t start; -// int32_t count; -// } uitextboxline_t; - -// typedef struct { -// uiframe_t frame; - -// font_t *font; -// color_t textColor; - -// float_t x, y, width, height; - -// char_t text[UI_TEXTBOX_TEXT_MAX]; - -// uitextboxline_t lines[UI_TEXTBOX_LINES_MAX]; -// int32_t lineCount; -// int32_t charsPerLine; -// int32_t linesPerPage; -// int32_t pageCount; - -// int32_t currentPage; -// int32_t scroll; -// inputaction_t advanceAction; - -// eventcallback_t onPageCompleteCallbacks[4]; -// void *onPageCompleteUsers[4]; -// event_t onPageComplete; -// eventcallback_t onLastPageCallbacks[4]; -// void *onLastPageUsers[4]; -// event_t onLastPage; -// } uitextbox_t; - -// extern uitextbox_t UI_TEXTBOX; - -// /** -// * Initializes UI_TEXTBOX. Sets position and size from current screen -// * dimensions and wires up the default font and frame textures. -// * Call after displayInit(). -// * -// * @return Any error that occurs. -// */ -// errorret_t uiTextboxInit(void); - -// /** -// * Rebuilds the word-wrap and page layout from the current text and dimensions. -// * Called automatically by uiTextboxSetText. -// */ -// void uiTextboxBuildLayout(void); - -// /** -// * Copies text into UI_TEXTBOX and rebuilds the word-wrap / page layout. -// * Resets currentPage and scroll to 0. -// * -// * @param text Null-terminated source string. -// */ -// void uiTextboxSetText(const char_t *text); - -// /** -// * Advances the typewriter scroll by UI_TEXTBOX_SCROLL_CHARS_PER_TICK. -// * Skipped on dynamic ticks when DUSK_TIME_DYNAMIC is defined. -// * -// * @return Any error that occurs. -// */ -// errorret_t uiTextboxUpdate(void); - -// /** -// * Draws the frame and the currently visible text, including a final flush. -// * -// * @return Any error that occurs. -// */ -// errorret_t uiTextboxDraw(void); - -// /** -// * Returns the total char count for the current page. -// * -// * @return Total chars on current page. -// */ -// int32_t uiTextboxGetPageCharCount(void); - -// /** -// * Returns true when scroll has fully revealed the current page. -// */ -// bool_t uiTextboxPageIsComplete(void); - -// /** -// * Returns true when there is at least one more page after the current one. -// */ -// bool_t uiTextboxHasNextPage(void); - -// /** -// * Advances to the next page and resets scroll to 0. -// * Has no effect if already on the last page. -// */ -// void uiTextboxNextPage(void); - -// /** -// * Disposes of UI_TEXTBOX, nulling out texture pointers. -// * -// * @return Any error that occurs. -// */ -// errorret_t uiTextboxDispose(void); -- 2.52.0 From a74f1568e9264b352c13e99825a7b180a47fe4de Mon Sep 17 00:00:00 2001 From: Dominic Masters Date: Thu, 25 Jun 2026 21:30:44 -0500 Subject: [PATCH 11/16] Small improvements to performance --- src/dusk/rpg/entity/entityinteract.h | 2 +- src/dusk/rpg/overworld/chunk.h | 2 +- src/dusk/rpg/overworld/worldpos.h | 2 +- src/dusk/rpg/rpg.c | 2 +- src/dusk/ui/widget/uibutton.h | 2 -- src/duskgl/display/shader/shadergl.c | 27 ++++++++++++++------------- src/duskgl/display/shader/shadergl.h | 1 + 7 files changed, 19 insertions(+), 19 deletions(-) diff --git a/src/dusk/rpg/entity/entityinteract.h b/src/dusk/rpg/entity/entityinteract.h index 27a1efd6..8d18fefa 100644 --- a/src/dusk/rpg/entity/entityinteract.h +++ b/src/dusk/rpg/entity/entityinteract.h @@ -26,7 +26,7 @@ typedef enum { */ typedef union { const cutscene_t *cutscene; - char_t message[32]; + const char_t *message; } entityinteractdata_t; /** diff --git a/src/dusk/rpg/overworld/chunk.h b/src/dusk/rpg/overworld/chunk.h index fc5cbb30..baf82341 100644 --- a/src/dusk/rpg/overworld/chunk.h +++ b/src/dusk/rpg/overworld/chunk.h @@ -12,7 +12,7 @@ #include "display/spritebatch/spritebatch.h" // #define CHUNK_MESH_COUNT_MAX 3 -#define CHUNK_VERTEX_COUNT (QUAD_VERTEX_COUNT * CHUNK_TILE_COUNT) +#define CHUNK_VERTEX_COUNT (QUAD_VERTEX_COUNT * CHUNK_WIDTH * CHUNK_HEIGHT * 2) #define CHUNK_ENTITY_COUNT_MAX 10 typedef struct chunk_s { diff --git a/src/dusk/rpg/overworld/worldpos.h b/src/dusk/rpg/overworld/worldpos.h index 8dae69f4..1543bf2f 100644 --- a/src/dusk/rpg/overworld/worldpos.h +++ b/src/dusk/rpg/overworld/worldpos.h @@ -19,7 +19,7 @@ #define MAP_CHUNK_DEPTH 3 #define MAP_CHUNK_COUNT (MAP_CHUNK_WIDTH * MAP_CHUNK_HEIGHT * MAP_CHUNK_DEPTH) -#define ENTITY_COUNT 64 +#define ENTITY_COUNT 32 typedef int16_t worldunit_t; typedef int16_t chunkunit_t; diff --git a/src/dusk/rpg/rpg.c b/src/dusk/rpg/rpg.c index e4f95b00..4fd6162b 100644 --- a/src/dusk/rpg/rpg.c +++ b/src/dusk/rpg/rpg.c @@ -43,7 +43,7 @@ errorret_t rpgInit(void) { entityInit(npc, ENTITY_TYPE_NPC); npc->position = (worldpos_t){ 3, 3, 0 }; npc->interact.type = ENTITY_INTERACT_PRINT; - stringCopy(npc->interact.data.message, "hello world", 32); + npc->interact.data.message = "hello world"; // All Good! errorOk(); diff --git a/src/dusk/ui/widget/uibutton.h b/src/dusk/ui/widget/uibutton.h index b8a28268..0085f564 100644 --- a/src/dusk/ui/widget/uibutton.h +++ b/src/dusk/ui/widget/uibutton.h @@ -8,8 +8,6 @@ #pragma once #include "error/error.h" -#define UI_BUTTON_LABEL_MAX 32 - typedef struct { const char_t *label; bool_t highlighted; diff --git a/src/duskgl/display/shader/shadergl.c b/src/duskgl/display/shader/shadergl.c index 15b5e2ec..f7467f41 100644 --- a/src/duskgl/display/shader/shadergl.c +++ b/src/duskgl/display/shader/shadergl.c @@ -223,23 +223,24 @@ errorret_t shaderSetTextureGL( "Only one texture supported in legacy opengl." ); - // glActiveTexture(GL_TEXTURE0); - errorChain(errorGLCheck()); - if(texture == NULL) { - glDisable(GL_TEXTURE_2D); - errorChain(errorGLCheck()); - + if(SHADER_LEGACY.boundTextureId != 0) { + glDisable(GL_TEXTURE_2D); + errorChain(errorGLCheck()); + SHADER_LEGACY.boundTextureId = 0; + } errorOk(); } - glEnable(GL_TEXTURE_2D); - errorChain(errorGLCheck()); - glBindTexture(GL_TEXTURE_2D, texture->id); - errorChain(errorGLCheck()); - - glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE); - errorChain(errorGLCheck()); + if(texture->id != SHADER_LEGACY.boundTextureId) { + glEnable(GL_TEXTURE_2D); + errorChain(errorGLCheck()); + glBindTexture(GL_TEXTURE_2D, texture->id); + errorChain(errorGLCheck()); + glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE); + errorChain(errorGLCheck()); + SHADER_LEGACY.boundTextureId = texture->id; + } // glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_COMBINE); // errorChain(errorGLCheck()); diff --git a/src/duskgl/display/shader/shadergl.h b/src/duskgl/display/shader/shadergl.h index bc1d0a65..11be1a43 100644 --- a/src/duskgl/display/shader/shadergl.h +++ b/src/duskgl/display/shader/shadergl.h @@ -48,6 +48,7 @@ typedef struct shadergl_s { typedef struct { shadergl_t *boundShader; uint_fast8_t dirty; + GLuint boundTextureId; } shaderlegacygl_t; extern shaderlegacygl_t SHADER_LEGACY; -- 2.52.0 From 454b8a91ba616c1380329d6a57531f66f1139be3 Mon Sep 17 00:00:00 2001 From: Dominic Masters Date: Thu, 25 Jun 2026 22:21:12 -0500 Subject: [PATCH 12/16] Fixed dolphin ratioing --- cmake/targets/dolphin.cmake | 7 ++++-- src/dusk/display/screen/screen.c | 28 ++++++++++++++++------- src/dusk/engine/engine.c | 2 +- src/dusk/rpg/overworld/worldpos.h | 8 ++++--- src/dusk/scene/overworld/sceneoverworld.c | 16 ++++++++++--- src/dusk/ui/overlay/uicrop.c | 4 ++++ src/duskdolphin/system/systemdolphin.c | 15 ++++++++++++ 7 files changed, 63 insertions(+), 17 deletions(-) diff --git a/cmake/targets/dolphin.cmake b/cmake/targets/dolphin.cmake index ff6164ba..a20b9d81 100644 --- a/cmake/targets/dolphin.cmake +++ b/cmake/targets/dolphin.cmake @@ -7,8 +7,11 @@ set_property(CACHE DUSK_DOLPHIN_BUILD_TYPE PROPERTY STRINGS "DOL" "ISO") target_compile_definitions(${DUSK_LIBRARY_TARGET_NAME} PUBLIC DUSK_DOLPHIN DUSK_INPUT_GAMEPAD - DUSK_DISPLAY_WIDTH=640 - DUSK_DISPLAY_HEIGHT=480 + # DUSK_DISPLAY_WIDTH=640 + # DUSK_DISPLAY_HEIGHT=480 + DUSK_DISPLAY_WIDTH=320 + DUSK_DISPLAY_HEIGHT=240 + DUSK_DISPLAY_OVERSCAN=16 DUSK_THREAD_PTHREAD DOL=1 ISO=2 diff --git a/src/dusk/display/screen/screen.c b/src/dusk/display/screen/screen.c index 79920a12..1690ea4a 100644 --- a/src/dusk/display/screen/screen.c +++ b/src/dusk/display/screen/screen.c @@ -56,10 +56,15 @@ errorret_t screenBind() { // Assume backbuffer is currently bound. switch(SCREEN.mode) { case SCREEN_MODE_BACKBUFFER: { - // Screen mode backbuffer uses the full display size - SCREEN.width = frameBufferGetWidth(FRAMEBUFFER_BOUND); - SCREEN.height = frameBufferGetHeight(FRAMEBUFFER_BOUND); - SCREEN.aspect = frameBufferGetAspect(FRAMEBUFFER_BOUND); + #if defined(DUSK_DISPLAY_WIDTH) && defined(DUSK_DISPLAY_HEIGHT) + SCREEN.width = DUSK_DISPLAY_WIDTH; + SCREEN.height = DUSK_DISPLAY_HEIGHT; + SCREEN.aspect = (float_t)DUSK_DISPLAY_WIDTH / (float_t)DUSK_DISPLAY_HEIGHT; + #else + SCREEN.width = frameBufferGetWidth(FRAMEBUFFER_BOUND); + SCREEN.height = frameBufferGetHeight(FRAMEBUFFER_BOUND); + SCREEN.aspect = frameBufferGetAspect(FRAMEBUFFER_BOUND); + #endif screenUpdateScan(); // No needd for a framebuffer. @@ -407,10 +412,17 @@ errorret_t screenRender() { } void screenUpdateScan(void) { - SCREEN.scanX = 0; - SCREEN.scanY = 0; - SCREEN.scanWidth = SCREEN.width; - SCREEN.scanHeight = SCREEN.height; + #ifdef DUSK_DISPLAY_OVERSCAN + SCREEN.scanX = DUSK_DISPLAY_OVERSCAN; + SCREEN.scanY = DUSK_DISPLAY_OVERSCAN; + SCREEN.scanWidth = SCREEN.width - DUSK_DISPLAY_OVERSCAN * 2; + SCREEN.scanHeight = SCREEN.height - DUSK_DISPLAY_OVERSCAN * 2; + #else + SCREEN.scanX = 0; + SCREEN.scanY = 0; + SCREEN.scanWidth = SCREEN.width; + SCREEN.scanHeight = SCREEN.height; + #endif #ifdef DUSK_DISPLAY_SIZE_DYNAMIC // Find the smallest standard ratio >= the screen aspect. diff --git a/src/dusk/engine/engine.c b/src/dusk/engine/engine.c index 04c234ad..54c59f3e 100644 --- a/src/dusk/engine/engine.c +++ b/src/dusk/engine/engine.c @@ -33,9 +33,9 @@ errorret_t engineInit(const int32_t argc, const char_t **argv) { ENGINE.version = DUSK_VERSION; // Init systems. Order is important. - errorChain(systemInit()); timeInit(); consoleInit(); + errorChain(systemInit()); errorChain(inputInit()); errorChain(assetInit()); // errorChain(saveInit()); diff --git a/src/dusk/rpg/overworld/worldpos.h b/src/dusk/rpg/overworld/worldpos.h index 1543bf2f..8008312e 100644 --- a/src/dusk/rpg/overworld/worldpos.h +++ b/src/dusk/rpg/overworld/worldpos.h @@ -8,14 +8,16 @@ #pragma once #include "dusk.h" -#define CHUNK_WIDTH 16 -#define CHUNK_HEIGHT CHUNK_WIDTH +#define TILE_SIZE_PIXELS 24 + +#define CHUNK_WIDTH 12 +#define CHUNK_HEIGHT 16 #define CHUNK_DEPTH 8 #define CHUNK_TILE_COUNT (CHUNK_WIDTH * CHUNK_HEIGHT * CHUNK_DEPTH) #define MAP_CHUNK_WIDTH 5 -#define MAP_CHUNK_HEIGHT 5 +#define MAP_CHUNK_HEIGHT 4 #define MAP_CHUNK_DEPTH 3 #define MAP_CHUNK_COUNT (MAP_CHUNK_WIDTH * MAP_CHUNK_HEIGHT * MAP_CHUNK_DEPTH) diff --git a/src/dusk/scene/overworld/sceneoverworld.c b/src/dusk/scene/overworld/sceneoverworld.c index feca011c..e2e83570 100644 --- a/src/dusk/scene/overworld/sceneoverworld.c +++ b/src/dusk/scene/overworld/sceneoverworld.c @@ -58,12 +58,22 @@ errorret_t sceneOverworldRender(scenedata_t *sceneData) { errorChain(shaderSetMatrix(&SHADER_UNLIT, SHADER_UNLIT_PROJECTION, proj)); // Camera Eye - float_t pixelsPerUnit = 16.0f; - float_t worldH = (float)SCREEN.height / pixelsPerUnit; + float_t pixelsPerUnit = TILE_SIZE_PIXELS; + float_t worldH = (float_t)SCREEN.height / pixelsPerUnit; float_t z = (worldH * 0.5f) / tanf(fov * 0.5f); vec3 worldPosVec; rpgCameraGetPosition(worldPosVec); - float_t offset = -16.0f; + float_t offset = -32.0f * (worldH / TILE_SIZE_PIXELS); + + // consolePrint( + // "FOV: %f, Aspect: %f, WorldH: %f, Z: %f, Offset: %f, ScreenH: %d", + // fov, + // (float_t)SCREEN.width / (float_t)SCREEN.height, + // worldH, + // z, + // offset, + // SCREEN.height + // ); glm_vec3_add(worldPosVec, (vec3){ 0.5f, 0.5f, 0.5f }, worldPosVec); diff --git a/src/dusk/ui/overlay/uicrop.c b/src/dusk/ui/overlay/uicrop.c index f37bf310..3e8d388b 100644 --- a/src/dusk/ui/overlay/uicrop.c +++ b/src/dusk/ui/overlay/uicrop.c @@ -19,6 +19,10 @@ errorret_t uiCropInit(void) { } errorret_t uiCropDraw(void) { + #ifndef DUSK_DISPLAY_SIZE_DYNAMIC + errorOk(); + #endif + if( SCREEN.scanX == 0 && SCREEN.scanY == 0 && diff --git a/src/duskdolphin/system/systemdolphin.c b/src/duskdolphin/system/systemdolphin.c index 1adfd1f6..19915b16 100644 --- a/src/duskdolphin/system/systemdolphin.c +++ b/src/duskdolphin/system/systemdolphin.c @@ -9,8 +9,23 @@ #include "input/input.h" #include "util/string.h" #include "assert/assert.h" +#include "console/console.h" errorret_t systemInitDolphin(void) { + char_t *names[] = { + "4:3", + "16:9", + "Unknown" + }; + + int32_t ratio = systemGetAspectRatioDolphin(); + uint8_t i = ( + ratio == CONF_ASPECT_4_3 ? 0 : + ratio == CONF_ASPECT_16_9 ? 1 : + 2 + ); + consolePrint("Dolphin Aspect Ratio: %s", names[i]); + errorOk(); } -- 2.52.0 From 0c8c0d24bad822d30641a8eda03c215524e1661f Mon Sep 17 00:00:00 2001 From: Dominic Masters Date: Thu, 25 Jun 2026 22:29:13 -0500 Subject: [PATCH 13/16] fix chunk loading based on player position --- src/dusk/rpg/rpgcamera.c | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/src/dusk/rpg/rpgcamera.c b/src/dusk/rpg/rpgcamera.c index 89058643..b64ac9b0 100644 --- a/src/dusk/rpg/rpgcamera.c +++ b/src/dusk/rpg/rpgcamera.c @@ -45,14 +45,17 @@ errorret_t rpgCameraUpdate(void) { vec3 pos; rpgCameraGetPosition(pos); - worldpos_t worldPos = { - (worldunit_t)pos[0], - (worldunit_t)pos[1], - (worldunit_t)pos[2] + + // Round to the nearest chunk center rather than flooring, so the + // loaded window is always most centered on what the camera sees. + // Adding half the chunk dimension before flooring snaps at the + // midpoint of each chunk instead of at the boundary. + chunkpos_t chunkPos = { + .x = (chunkunit_t)floorf(pos[0] / CHUNK_WIDTH + 0.5f), + .y = (chunkunit_t)floorf(pos[1] / CHUNK_HEIGHT + 0.5f), + .z = (chunkunit_t)floorf(pos[2] / CHUNK_DEPTH + 0.5f) }; - chunkpos_t chunkPos; - worldPosToChunkPos(&worldPos, &chunkPos); - + errorChain(mapPositionSet((chunkpos_t){ .x = chunkPos.x - (MAP_CHUNK_WIDTH / 2), .y = chunkPos.y - (MAP_CHUNK_HEIGHT / 2), -- 2.52.0 From 24631990bdb8448a8edc81f626aba2a4bfc65b2e Mon Sep 17 00:00:00 2001 From: Dominic Masters Date: Thu, 25 Jun 2026 22:47:23 -0500 Subject: [PATCH 14/16] Fixed dolphin aspect ratio --- src/dusk/display/screen/screen.c | 11 +++++------ src/dusk/rpg/overworld/worldpos.h | 4 ++-- src/dusk/scene/overworld/sceneoverworld.c | 14 ++------------ 3 files changed, 9 insertions(+), 20 deletions(-) diff --git a/src/dusk/display/screen/screen.c b/src/dusk/display/screen/screen.c index 1690ea4a..5b2e233e 100644 --- a/src/dusk/display/screen/screen.c +++ b/src/dusk/display/screen/screen.c @@ -57,9 +57,9 @@ errorret_t screenBind() { switch(SCREEN.mode) { case SCREEN_MODE_BACKBUFFER: { #if defined(DUSK_DISPLAY_WIDTH) && defined(DUSK_DISPLAY_HEIGHT) - SCREEN.width = DUSK_DISPLAY_WIDTH; SCREEN.height = DUSK_DISPLAY_HEIGHT; - SCREEN.aspect = (float_t)DUSK_DISPLAY_WIDTH / (float_t)DUSK_DISPLAY_HEIGHT; + SCREEN.aspect = frameBufferGetAspect(FRAMEBUFFER_BOUND); + SCREEN.width = SCREEN.height * SCREEN.aspect; #else SCREEN.width = frameBufferGetWidth(FRAMEBUFFER_BOUND); SCREEN.height = frameBufferGetHeight(FRAMEBUFFER_BOUND); @@ -415,14 +415,13 @@ void screenUpdateScan(void) { #ifdef DUSK_DISPLAY_OVERSCAN SCREEN.scanX = DUSK_DISPLAY_OVERSCAN; SCREEN.scanY = DUSK_DISPLAY_OVERSCAN; - SCREEN.scanWidth = SCREEN.width - DUSK_DISPLAY_OVERSCAN * 2; - SCREEN.scanHeight = SCREEN.height - DUSK_DISPLAY_OVERSCAN * 2; #else SCREEN.scanX = 0; SCREEN.scanY = 0; - SCREEN.scanWidth = SCREEN.width; - SCREEN.scanHeight = SCREEN.height; #endif + + SCREEN.scanWidth = SCREEN.width - (SCREEN.scanX * 2); + SCREEN.scanHeight = SCREEN.height - (SCREEN.scanY * 2); #ifdef DUSK_DISPLAY_SIZE_DYNAMIC // Find the smallest standard ratio >= the screen aspect. diff --git a/src/dusk/rpg/overworld/worldpos.h b/src/dusk/rpg/overworld/worldpos.h index 8008312e..bb6be2fe 100644 --- a/src/dusk/rpg/overworld/worldpos.h +++ b/src/dusk/rpg/overworld/worldpos.h @@ -10,13 +10,13 @@ #define TILE_SIZE_PIXELS 24 -#define CHUNK_WIDTH 12 +#define CHUNK_WIDTH 16 #define CHUNK_HEIGHT 16 #define CHUNK_DEPTH 8 #define CHUNK_TILE_COUNT (CHUNK_WIDTH * CHUNK_HEIGHT * CHUNK_DEPTH) -#define MAP_CHUNK_WIDTH 5 +#define MAP_CHUNK_WIDTH 4 #define MAP_CHUNK_HEIGHT 4 #define MAP_CHUNK_DEPTH 3 #define MAP_CHUNK_COUNT (MAP_CHUNK_WIDTH * MAP_CHUNK_HEIGHT * MAP_CHUNK_DEPTH) diff --git a/src/dusk/scene/overworld/sceneoverworld.c b/src/dusk/scene/overworld/sceneoverworld.c index e2e83570..e75e8def 100644 --- a/src/dusk/scene/overworld/sceneoverworld.c +++ b/src/dusk/scene/overworld/sceneoverworld.c @@ -50,7 +50,7 @@ errorret_t sceneOverworldRender(scenedata_t *sceneData) { float_t fov = glm_rad(45.0f); glm_perspective( fov, - (float_t)SCREEN.width / (float_t)SCREEN.height, + SCREEN.aspect, 0.1f, 100.0f, proj @@ -63,17 +63,7 @@ errorret_t sceneOverworldRender(scenedata_t *sceneData) { float_t z = (worldH * 0.5f) / tanf(fov * 0.5f); vec3 worldPosVec; rpgCameraGetPosition(worldPosVec); - float_t offset = -32.0f * (worldH / TILE_SIZE_PIXELS); - - // consolePrint( - // "FOV: %f, Aspect: %f, WorldH: %f, Z: %f, Offset: %f, ScreenH: %d", - // fov, - // (float_t)SCREEN.width / (float_t)SCREEN.height, - // worldH, - // z, - // offset, - // SCREEN.height - // ); + float_t offset = -24.0f * (worldH / TILE_SIZE_PIXELS); glm_vec3_add(worldPosVec, (vec3){ 0.5f, 0.5f, 0.5f }, worldPosVec); -- 2.52.0 From 2fe543a60315675d271b3c6a541875d41b62c570 Mon Sep 17 00:00:00 2001 From: Dominic Masters Date: Fri, 26 Jun 2026 08:44:26 -0500 Subject: [PATCH 15/16] UI Transition --- src/dusk/rpg/item/inventory.h | 2 +- src/dusk/ui/CMakeLists.txt | 1 + src/dusk/ui/transition/CMakeLists.txt | 10 +++ src/dusk/ui/transition/uitransition.c | 81 +++++++++++++++++++++++ src/dusk/ui/transition/uitransition.h | 74 +++++++++++++++++++++ src/dusk/ui/transition/uitransitiondata.h | 23 +++++++ src/dusk/ui/transition/uitransitionfade.c | 56 ++++++++++++++++ src/dusk/ui/transition/uitransitionfade.h | 29 ++++++++ src/dusk/ui/uielement.c | 7 ++ 9 files changed, 282 insertions(+), 1 deletion(-) create mode 100644 src/dusk/ui/transition/CMakeLists.txt create mode 100644 src/dusk/ui/transition/uitransition.c create mode 100644 src/dusk/ui/transition/uitransition.h create mode 100644 src/dusk/ui/transition/uitransitiondata.h create mode 100644 src/dusk/ui/transition/uitransitionfade.c create mode 100644 src/dusk/ui/transition/uitransitionfade.h diff --git a/src/dusk/rpg/item/inventory.h b/src/dusk/rpg/item/inventory.h index a56d93f4..ba559dfe 100644 --- a/src/dusk/rpg/item/inventory.h +++ b/src/dusk/rpg/item/inventory.h @@ -8,7 +8,7 @@ #pragma once #include "rpg/item/item.h" -#define ITEM_STACK_QUANTITY_MAX UINT8_MAX +#define ITEM_STACK_QUANTITY_MAX 99 typedef enum { INVENTORY_SORT_BY_ID, diff --git a/src/dusk/ui/CMakeLists.txt b/src/dusk/ui/CMakeLists.txt index d6a0d06f..26754f0d 100644 --- a/src/dusk/ui/CMakeLists.txt +++ b/src/dusk/ui/CMakeLists.txt @@ -8,6 +8,7 @@ add_subdirectory(frame) add_subdirectory(focus) add_subdirectory(overlay) add_subdirectory(rpg) +add_subdirectory(transition) add_subdirectory(widget) # Sources diff --git a/src/dusk/ui/transition/CMakeLists.txt b/src/dusk/ui/transition/CMakeLists.txt new file mode 100644 index 00000000..0e365038 --- /dev/null +++ b/src/dusk/ui/transition/CMakeLists.txt @@ -0,0 +1,10 @@ +# Copyright (c) 2026 Dominic Masters +# +# This software is released under the MIT License. +# https://opensource.org/licenses/MIT + +target_sources(${DUSK_LIBRARY_TARGET_NAME} + PUBLIC + uitransition.c + uitransitionfade.c +) diff --git a/src/dusk/ui/transition/uitransition.c b/src/dusk/ui/transition/uitransition.c new file mode 100644 index 00000000..4ed0026e --- /dev/null +++ b/src/dusk/ui/transition/uitransition.c @@ -0,0 +1,81 @@ +/** + * Copyright (c) 2026 Dominic Masters + * + * This software is released under the MIT License. + * https://opensource.org/licenses/MIT + */ + +#include "uitransition.h" +#include "uitransitionfade.h" +#include "assert/assert.h" +#include "util/memory.h" +#include "time/time.h" +#include "util/math.h" + +uitransition_t UI_TRANSITION; + +uitransitiondrawfunc_t UI_TRANSITION_TYPE_DRAW[UI_TRANSITION_TYPE_COUNT] = { + [UI_TRANSITION_TYPE_FADE] = uiTransitionFadeDraw +}; + +errorret_t uiTransitionInit(void) { + memoryZero(&UI_TRANSITION, sizeof(uitransition_t)); + + // uiTransitionStart((uitransitionstartparams_t){ + // .duration = 5.0f, + // .type = UI_TRANSITION_TYPE_FADE, + // .params = { + // .fade = { + // .easing = EASING_OUT_QUART, + // .fromColor = COLOR_TRANSPARENT_BLACK, + // .toColor = COLOR_BLACK + // } + // }, + // .finished = NULL, + // .user = NULL + // }); + + errorOk(); +} + +errorret_t uiTransitionUpdate(void) { + // Update time. + float_t newTime = UI_TRANSITION.data.time + TIME.delta; + + UI_TRANSITION.data.time = mathClamp( + newTime, + 0.0f, + UI_TRANSITION.data.duration + ); + UI_TRANSITION.data.t = ( + (UI_TRANSITION.data.duration <= 0.0f) ? + 1.0f : + (UI_TRANSITION.data.time / UI_TRANSITION.data.duration) + ); + + if(UI_TRANSITION.data.t >= 1.0f && UI_TRANSITION.finished) { + UI_TRANSITION.finished(UI_TRANSITION.user); + } + + errorOk(); +} + +errorret_t uiTransitionDraw(void) { + uitransitiondrawfunc_t draw = UI_TRANSITION_TYPE_DRAW[UI_TRANSITION.type]; + if(!draw) errorOk(); + return draw(&UI_TRANSITION.data); +} + +void uiTransitionStart(const uitransitionstartparams_t info) { + assertTrue(info.type > UI_TRANSITION_TYPE_NULL, "Invalid type"); + assertTrue(info.type < UI_TRANSITION_TYPE_COUNT, "Invalid type"); + assertTrue(info.duration >= 0.0f, "Duration must be non-negative"); + + memoryZero(&UI_TRANSITION, sizeof(uitransition_t)); + + UI_TRANSITION.data.duration = info.duration; + UI_TRANSITION.type = info.type; + UI_TRANSITION.data.params = info.params; + UI_TRANSITION.finished = info.finished; + UI_TRANSITION.user = info.user; +} \ No newline at end of file diff --git a/src/dusk/ui/transition/uitransition.h b/src/dusk/ui/transition/uitransition.h new file mode 100644 index 00000000..305ec5c3 --- /dev/null +++ b/src/dusk/ui/transition/uitransition.h @@ -0,0 +1,74 @@ +/** + * Copyright (c) 2026 Dominic Masters + * + * This software is released under the MIT License. + * https://opensource.org/licenses/MIT + */ + +#pragma once +#include "error/error.h" +#include "display/color.h" +#include "animation/easing.h" +#include "uitransitiondata.h" + +typedef void (*uitransitioncallback_t)(void *user); + +typedef enum { + UI_TRANSITION_TYPE_NULL, + + UI_TRANSITION_TYPE_FADE, + + UI_TRANSITION_TYPE_COUNT +} uitransitiontype_t; + +extern uitransitiondrawfunc_t UI_TRANSITION_TYPE_DRAW[UI_TRANSITION_TYPE_COUNT]; + + +typedef struct { + uitransitiondata_t data; + uitransitiontype_t type; + uitransitioncallback_t finished; + void *user; +} uitransition_t; + +extern uitransition_t UI_TRANSITION; + +/** + * Initializes the transition overlay, zeroing all fields and wiring the + * onTransitionEnd event. + * + * @return Any error that occurs. + */ +errorret_t uiTransitionInit(void); + +/** + * Advances the transition. Fires onTransitionEnd once when it completes. + * Safe to call when no transition is running. + * + * @return Any error that occurs. + */ +errorret_t uiTransitionUpdate(void); + +/** + * Renders the transition overlay. Skipped entirely when the current alpha + * is zero. + * + * @return Any error that occurs. + */ +errorret_t uiTransitionDraw(void); + +typedef struct { + float_t duration; + uitransitiontype_t type; + uitransitionparams_t params; + uitransitioncallback_t finished; + void *user; +} uitransitionstartparams_t; + +/** + * Starts a transition described by an info struct. + * + * @param info Transition parameters. + */ +void uiTransitionStart(const uitransitionstartparams_t info); + diff --git a/src/dusk/ui/transition/uitransitiondata.h b/src/dusk/ui/transition/uitransitiondata.h new file mode 100644 index 00000000..37e8430f --- /dev/null +++ b/src/dusk/ui/transition/uitransitiondata.h @@ -0,0 +1,23 @@ +/** + * Copyright (c) 2026 Dominic Masters + * + * This software is released under the MIT License. + * https://opensource.org/licenses/MIT + */ + +#pragma once +#include "error/error.h" +#include "uitransitionfade.h" + +typedef union uitransitionparams_u { + uitransitionfade_t fade; +} uitransitionparams_t; + +typedef struct uitransitiondata_s { + float_t time; + float_t duration; + float_t t; + uitransitionparams_t params; +} uitransitiondata_t; + +typedef errorret_t (*uitransitiondrawfunc_t)(const uitransitiondata_t *data); diff --git a/src/dusk/ui/transition/uitransitionfade.c b/src/dusk/ui/transition/uitransitionfade.c new file mode 100644 index 00000000..45e3168d --- /dev/null +++ b/src/dusk/ui/transition/uitransitionfade.c @@ -0,0 +1,56 @@ +/** + * Copyright (c) 2026 Dominic Masters + * + * This software is released under the MIT License. + * https://opensource.org/licenses/MIT + */ + +#include "uitransition.h" +#include "assert/assert.h" +#include "display/screen/screen.h" +#include "display/texture/texture.h" +#include "display/spritebatch/spritebatch.h" +#include "display/shader/shaderunlit.h" + +errorret_t uiTransitionFadeDraw(const uitransitiondata_t *data) { + assertNotNull(data, "data must not be NULL"); + + float_t e = easingApply(data->params.fade.easing, data->t); + + float_t r = (float_t)( + data->params.fade.toColor.r - data->params.fade.fromColor.r + ) * e; + float_t g = (float_t)( + data->params.fade.toColor.g - data->params.fade.fromColor.g + ) * e; + float_t b = (float_t)( + data->params.fade.toColor.b - data->params.fade.fromColor.b + ) * e; + float_t a = (float_t)( + data->params.fade.toColor.a - data->params.fade.fromColor.a + ) * e; + + color_t color = color4b( + data->params.fade.fromColor.r + (uint8_t)r, + data->params.fade.fromColor.g + (uint8_t)g, + data->params.fade.fromColor.b + (uint8_t)b, + data->params.fade.fromColor.a + (uint8_t)a + ); + + if(color.a == 0) errorOk(); + + spritebatchsprite_t sprite = { + .min = { 0.0f, 0.0f, 0.0f }, + .max = { (float_t)SCREEN.width, (float_t)SCREEN.height, 0.0f }, + .uvMin = { 0.0f, 0.0f }, + .uvMax = { 1.0f, 1.0f } + }; + shadermaterial_t material = { + .unlit = { + .color = color, + .texture = NULL + } + }; + errorChain(spriteBatchBuffer(&sprite, 1, &SHADER_UNLIT, material)); + return spriteBatchFlush(); +} diff --git a/src/dusk/ui/transition/uitransitionfade.h b/src/dusk/ui/transition/uitransitionfade.h new file mode 100644 index 00000000..a1b2475a --- /dev/null +++ b/src/dusk/ui/transition/uitransitionfade.h @@ -0,0 +1,29 @@ +/** + * Copyright (c) 2026 Dominic Masters + * + * This software is released under the MIT License. + * https://opensource.org/licenses/MIT + */ + +#pragma once +#include "error/error.h" +#include "display/color.h" +#include "animation/easing.h" + +typedef struct uitransitiondata_s uitransitiondata_t; + +typedef struct { + color_t fromColor; + color_t toColor; + easingtype_t easing; +} uitransitionfade_t; + +/** + * Renders a full-screen color overlay interpolated between fade->fromColor + * and fade->toColor using fade->easing. t is the raw normalized progress + * in [0, 1] before easing is applied. No-op when the resulting alpha is zero. + * + * @param fade The fade parameters. + * @return Any error that occurs. + */ +errorret_t uiTransitionFadeDraw(const uitransitiondata_t *fade); diff --git a/src/dusk/ui/uielement.c b/src/dusk/ui/uielement.c index 5c73e0bd..60eae45d 100644 --- a/src/dusk/ui/uielement.c +++ b/src/dusk/ui/uielement.c @@ -14,6 +14,7 @@ #include "ui/overlay/uiloading.h" #include "ui/debug/uiplayerpos.h" #include "ui/overlay/uicrop.h" +#include "ui/transition/uitransition.h" #include "ui/debug/uiconsole.h" #include "ui/frame/uisettings.h" #include "ui/rpg/uitextboxmain.h" @@ -44,6 +45,12 @@ uielement_t UI_ELEMENTS[] = { .draw = uiTextboxMainDraw }, + { + .init = uiTransitionInit, + .update = uiTransitionUpdate, + .draw = uiTransitionDraw + }, + // Fullbox over: above absolutely everything. { .init = uiFullboxOverInit, -- 2.52.0 From 1e890f6d86ca3e77cd3cfb7bcfde30c43cbf1dcf Mon Sep 17 00:00:00 2001 From: Dominic Masters Date: Fri, 26 Jun 2026 09:04:57 -0500 Subject: [PATCH 16/16] Backpack changes --- src/dusk/rpg/item/backpack.c | 69 +++++++++++++++++++++++++++-- src/dusk/rpg/item/backpack.h | 86 ++++++++++++++++++++++++++++++++++-- tools/item/csv/__main__.py | 21 ++++++--- 3 files changed, 164 insertions(+), 12 deletions(-) diff --git a/src/dusk/rpg/item/backpack.c b/src/dusk/rpg/item/backpack.c index 4a36e0e8..4ed1be20 100644 --- a/src/dusk/rpg/item/backpack.c +++ b/src/dusk/rpg/item/backpack.c @@ -7,9 +7,72 @@ #include "backpack.h" -inventorystack_t BACKPACK_STORAGE[BACKPACK_STORAGE_SIZE_MAX]; -inventory_t BACKPACK; +backpack_t BACKPACK; void backpackInit() { - inventoryInit(&BACKPACK, BACKPACK_STORAGE, BACKPACK_STORAGE_SIZE_MAX); + for(uint8_t i = 0; i < ITEM_TYPE_COUNT; i++) { + inventoryInit( + &BACKPACK.inventories[i], + BACKPACK.storage[i], + ITEM_TYPE_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"); + 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); +} + +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); +} + +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); +} + +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); +} + +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); +} + +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"); + 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); +} + +void backpackSort( + const itemtype_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"); + 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 4cb49358..5812befa 100644 --- a/src/dusk/rpg/item/backpack.h +++ b/src/dusk/rpg/item/backpack.h @@ -8,12 +8,90 @@ #pragma once #include "inventory.h" -#define BACKPACK_STORAGE_SIZE_MAX 20 +typedef struct { + inventorystack_t storage[ITEM_TYPE_COUNT][ITEM_TYPE_COUNT_MAX]; + inventory_t inventories[ITEM_TYPE_COUNT]; +} backpack_t; -extern inventorystack_t BACKPACK_STORAGE[BACKPACK_STORAGE_SIZE_MAX]; -extern inventory_t BACKPACK; +extern backpack_t BACKPACK; /** * Initializes the backpack inventory for the player. */ -void backpackInit(); \ No newline at end of file +void backpackInit(); + +/** + * Returns the inventory for a given item type. + * + * @param type The item type. + * @returns Pointer to the inventory for that type. + */ +inventory_t *backpackGetInventory(const itemtype_t type); + +/** + * Adds a quantity of an item to the backpack. + * + * @param item The item ID to add. + * @param quantity The quantity to add. + */ +void backpackAdd(const itemid_t item, const uint8_t quantity); + +/** + * Removes an item from the backpack. + * + * @param item The item ID to remove. + */ +void backpackRemove(const itemid_t item); + +/** + * Sets the quantity of an item in the backpack. + * + * @param item The item ID to set. + * @param quantity The quantity to set. + */ +void backpackSet(const itemid_t item, const uint8_t quantity); + +/** + * Gets the quantity of an item in the backpack. + * + * @param item The item ID to check. + * @returns The quantity held. + */ +uint8_t backpackGetCount(const itemid_t item); + +/** + * Checks if an item exists in the backpack (quantity > 0). + * + * @param item The item ID to check. + * @returns true if the item exists. + */ +bool_t backpackItemExists(const itemid_t item); + +/** + * Checks if the inventory for a given type is full. + * + * @param type The item type to check. + * @returns true if the type's inventory is full. + */ +bool_t backpackIsFull(const itemtype_t type); + +/** + * Checks if an item's stack is full in the backpack. + * + * @param item The item ID to check. + * @returns true if the item stack is full. + */ +bool_t backpackItemFull(const itemid_t item); + +/** + * Sorts the inventory for a given item type. + * + * @param type The item type whose inventory to sort. + * @param sortBy The sorting criteria. + * @param reverse Whether to sort in reverse order. + */ +void backpackSort( + const itemtype_t type, + const inventorysort_t sortBy, + const bool_t reverse +); \ No newline at end of file diff --git a/tools/item/csv/__main__.py b/tools/item/csv/__main__.py index bd3b75c7..40d0a0b8 100644 --- a/tools/item/csv/__main__.py +++ b/tools/item/csv/__main__.py @@ -48,6 +48,11 @@ for i in item_ids: count += 1 id_count = count +# 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", @@ -91,13 +96,19 @@ for i in item_ids: out += [ "};", "", - "static const char_t *ITEM_SCRIPT =", + "static const uint8_t ITEM_TYPE_COUNTS[] = {", ] -for i in item_ids: - out.append(f" \"{id_enum(i)} = {id_values[i]}\\n\"") for t in item_types: - out.append(f" \"{type_enum(t)} = {type_values[t]}\\n\"") -out += [";", ""] + 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: -- 2.52.0