From ff579719c97b2695036da8c7898be5cd3d25db76 Mon Sep 17 00:00:00 2001 From: a huge sonic fan Date: Sun, 28 Jun 2026 19:25:10 -0700 Subject: [PATCH 1/4] fix bullets --- src/runner.c | 561 +++++++++++++++++++++++++++------------------------ 1 file changed, 297 insertions(+), 264 deletions(-) diff --git a/src/runner.c b/src/runner.c index 609d17d2..387bd5a7 100644 --- a/src/runner.c +++ b/src/runner.c @@ -14,15 +14,15 @@ #include #include #ifdef __3DS__ -#include <3ds.h> -#include "n3ds/n3ds_platform_config.h" -#endif - -#ifndef N3DS_ENABLE_TILE_LAYER_CHUNK_CACHE -#define N3DS_ENABLE_TILE_LAYER_CHUNK_CACHE 0 -#endif - -#include "debug_overlay.h" +#include <3ds.h> +#include "n3ds/n3ds_platform_config.h" +#endif + +#ifndef N3DS_ENABLE_TILE_LAYER_CHUNK_CACHE +#define N3DS_ENABLE_TILE_LAYER_CHUNK_CACHE 0 +#endif + +#include "debug_overlay.h" // #include "stb_ds.h" #ifdef __3DS__ @@ -33,14 +33,14 @@ void N3DSRenderer_beginBottomScreenGUI2x(Renderer* renderer, int32_t guiW, int32 void N3DSRenderer_endBottomScreenGUI2x(Renderer* renderer); void N3DSRenderer_beginTopScreenGUI(Renderer* renderer, int32_t guiW, int32_t guiH); void N3DSRenderer_endTopScreenGUI(Renderer* renderer); -void N3DSRenderer_beginTopScreenGUI2x(Renderer* renderer, int32_t guiW, int32_t guiH); -void N3DSRenderer_endTopScreenGUI2x(Renderer* renderer); -bool N3DSRenderer_isTopScreenGUIActive(Renderer* renderer); -bool N3DSRenderer_isTopScreenBattleViewActive(Renderer* renderer); -void N3DSRenderer_setTopScreenBattleViewActive(Renderer* renderer, bool active); -int32_t N3DSRenderer_findTileEntryIndex(Renderer* renderer, int32_t backgroundIndex, int32_t srcX, int32_t srcY, int32_t srcW, int32_t srcH); -bool N3DSRenderer_drawCachedTileEntry(Renderer* renderer, int32_t tileEntryIndex, float drawX, float drawY, float xscale, float yscale, uint32_t color, float alpha); -int32_t N3DSRenderer_createTileLayerChunkCache(Renderer* renderer, int32_t roomWidth, int32_t roomHeight, const TileLayerRenderCache* cache); +void N3DSRenderer_beginTopScreenGUI2x(Renderer* renderer, int32_t guiW, int32_t guiH); +void N3DSRenderer_endTopScreenGUI2x(Renderer* renderer); +bool N3DSRenderer_isTopScreenGUIActive(Renderer* renderer); +bool N3DSRenderer_isTopScreenBattleViewActive(Renderer* renderer); +void N3DSRenderer_setTopScreenBattleViewActive(Renderer* renderer, bool active); +int32_t N3DSRenderer_findTileEntryIndex(Renderer* renderer, int32_t backgroundIndex, int32_t srcX, int32_t srcY, int32_t srcW, int32_t srcH); +bool N3DSRenderer_drawCachedTileEntry(Renderer* renderer, int32_t tileEntryIndex, float drawX, float drawY, float xscale, float yscale, uint32_t color, float alpha); +int32_t N3DSRenderer_createTileLayerChunkCache(Renderer* renderer, int32_t roomWidth, int32_t roomHeight, const TileLayerRenderCache* cache); bool N3DSRenderer_drawTileLayerChunkCache(Renderer* renderer, int32_t cacheId, float layerOffsetX, float layerOffsetY, float alpha); void N3DSRenderer_destroyTileLayerChunkCache(Renderer* renderer, int32_t cacheId); #endif @@ -103,24 +103,24 @@ static void Runner_executeResolvedEvent(Runner* runner, Instance* instance, int3 #ifdef __3DS__ //bottom screen battlefield offset factor -static const float k3DSBottomBattleFieldScale = 2.0f; -static const float k3DSBottomBattleFieldYOffset = -60.0f; +static const float k3DSBottomBattleFieldScale = 1.0f; +static const float k3DSBottomBattleFieldYOffset = 0.0f; static const float k3DSTopBattleEnemyInstanceYOffset = 112.0f; static bool Runner_stringContainsToken(const char* haystack, const char* needle) { return haystack != NULL && needle != NULL && strstr(haystack, needle) != NULL; } -static bool Runner_is3DSValidObjectIndex(Runner* runner, int32_t objectIndex); -static bool Runner_objectMatches3DSNameInHierarchy(Runner* runner, int32_t objectIndex, const char* name); -static bool Runner_objectContains3DSTokenInHierarchy(Runner* runner, int32_t objectIndex, const char* needle); -static bool Runner_is3DSLiveBattleBorderObject(Runner* runner, Instance* inst); -static bool Runner_shouldHideOn3DSTopScreen(Runner* runner, Instance* inst); -static bool Runner_is3DSBattleBackdropInstance(Runner* runner, Instance* inst); -static bool Runner_shouldOffset3DSTopBattleInstance(Runner* runner, Instance* inst); -static bool Runner_is3DSBattleFieldObject(Runner* runner, Instance* inst); -static bool Runner_is3DSAsrielBattle(Runner* runner); -static bool Runner_is3DSUndyneBattle(Runner* runner); +static bool Runner_is3DSValidObjectIndex(Runner* runner, int32_t objectIndex); +static bool Runner_objectMatches3DSNameInHierarchy(Runner* runner, int32_t objectIndex, const char* name); +static bool Runner_objectContains3DSTokenInHierarchy(Runner* runner, int32_t objectIndex, const char* needle); +static bool Runner_is3DSLiveBattleBorderObject(Runner* runner, Instance* inst); +static bool Runner_shouldHideOn3DSTopScreen(Runner* runner, Instance* inst); +static bool Runner_is3DSBattleBackdropInstance(Runner* runner, Instance* inst); +static bool Runner_shouldOffset3DSTopBattleInstance(Runner* runner, Instance* inst); +static bool Runner_is3DSBattleFieldObject(Runner* runner, Instance* inst); +static bool Runner_is3DSAsrielBattle(Runner* runner); +static bool Runner_is3DSUndyneBattle(Runner* runner); static RValue Runner_get3DSGlobal(Runner* runner, const char* name) { if (runner == NULL || runner->vmContext == NULL || name == NULL) return RValue_makeUndefined(); @@ -165,38 +165,38 @@ static bool Runner_is3DSDodgingBullets(Runner* runner) { return Runner_get3DSGlobalReal(runner, "turntimer") > 0.0; } -static void Runner_prepare3DSDrawBattleState(Runner* runner) { - if (runner == NULL || runner->n3dsDrawBattleStateValid) return; - - runner->n3dsDrawHasTopEnemyDialogue = false; - runner->n3dsDrawAsrielBattle = false; - bool hasLiveBattleBorder = false; - bool hasActiveBattleController = false; - if (runner->instances != NULL) { - int32_t instanceCount = (int32_t) arrlen(runner->instances); - repeat(instanceCount, i) { - Instance* inst = runner->instances[i]; - if (inst == NULL || inst->destroyed || !inst->active) continue; - if (Runner_is3DSLiveBattleBorderObject(runner, inst)) { - if (inst->visible) hasLiveBattleBorder = true; - } - if (inst->visible && - Runner_is3DSValidObjectIndex(runner, inst->objectIndex) && - !Runner_is3DSBattleBackdropInstance(runner, inst)) { - if (Runner_objectContains3DSTokenInHierarchy(runner, inst->objectIndex, "asriel")) { - runner->n3dsDrawAsrielBattle = true; - } else if (inst->spriteIndex >= 0 && (uint32_t) inst->spriteIndex < runner->dataWin->sprt.count) { - const char* spriteName = runner->dataWin->sprt.sprites[inst->spriteIndex].name; - if (Runner_stringContainsToken(spriteName, "asriel")) { - runner->n3dsDrawAsrielBattle = true; - } - } - } - if (Runner_is3DSValidObjectIndex(runner, inst->objectIndex) && - Runner_objectMatches3DSNameInHierarchy(runner, inst->objectIndex, "obj_battlecontroller") && - ((int32_t) Runner_get3DSInstanceRealVar(runner, inst, "drawrect") == 1 || - (int32_t) Runner_get3DSInstanceRealVar(runner, inst, "drawbinfo") == 1)) { - hasActiveBattleController = true; +static void Runner_prepare3DSDrawBattleState(Runner* runner) { + if (runner == NULL || runner->n3dsDrawBattleStateValid) return; + + runner->n3dsDrawHasTopEnemyDialogue = false; + runner->n3dsDrawAsrielBattle = false; + bool hasLiveBattleBorder = false; + bool hasActiveBattleController = false; + if (runner->instances != NULL) { + int32_t instanceCount = (int32_t) arrlen(runner->instances); + repeat(instanceCount, i) { + Instance* inst = runner->instances[i]; + if (inst == NULL || inst->destroyed || !inst->active) continue; + if (Runner_is3DSLiveBattleBorderObject(runner, inst)) { + if (inst->visible) hasLiveBattleBorder = true; + } + if (inst->visible && + Runner_is3DSValidObjectIndex(runner, inst->objectIndex) && + !Runner_is3DSBattleBackdropInstance(runner, inst)) { + if (Runner_objectContains3DSTokenInHierarchy(runner, inst->objectIndex, "asriel")) { + runner->n3dsDrawAsrielBattle = true; + } else if (inst->spriteIndex >= 0 && (uint32_t) inst->spriteIndex < runner->dataWin->sprt.count) { + const char* spriteName = runner->dataWin->sprt.sprites[inst->spriteIndex].name; + if (Runner_stringContainsToken(spriteName, "asriel")) { + runner->n3dsDrawAsrielBattle = true; + } + } + } + if (Runner_is3DSValidObjectIndex(runner, inst->objectIndex) && + Runner_objectMatches3DSNameInHierarchy(runner, inst->objectIndex, "obj_battlecontroller") && + ((int32_t) Runner_get3DSInstanceRealVar(runner, inst, "drawrect") == 1 || + (int32_t) Runner_get3DSInstanceRealVar(runner, inst, "drawbinfo") == 1)) { + hasActiveBattleController = true; } } } @@ -258,38 +258,38 @@ static bool Runner_objectContains3DSTokenInHierarchy(Runner* runner, int32_t obj return false; } -static bool Runner_is3DSInstanceInsideBattleField(Runner* runner, Instance* inst) { - if (runner == NULL || inst == NULL) return false; - - double left = Runner_get3DSGlobalArrayReal(runner, "idealborder", 0); - double right = Runner_get3DSGlobalArrayReal(runner, "idealborder", 1); +static bool Runner_is3DSInstanceInsideBattleField(Runner* runner, Instance* inst) { + if (runner == NULL || inst == NULL) return false; + + double left = Runner_get3DSGlobalArrayReal(runner, "idealborder", 0); + double right = Runner_get3DSGlobalArrayReal(runner, "idealborder", 1); double top = Runner_get3DSGlobalArrayReal(runner, "idealborder", 2); double bottom = Runner_get3DSGlobalArrayReal(runner, "idealborder", 3); if (right <= left || bottom <= top) return false; - - double marginX = 24.0; - double marginY = 24.0; - double expandedLeft = left - marginX; - double expandedRight = right + marginX; - double expandedTop = top - marginY; - double expandedBottom = bottom + marginY; - - InstanceBBox bbox = Collision_computeBBox(runner->dataWin, inst); - if (bbox.valid) { - double overlapLeft = fmax((double) bbox.left, expandedLeft); - double overlapRight = fmin((double) bbox.right, expandedRight); - double overlapTop = fmax((double) bbox.top, expandedTop); - double overlapBottom = fmin((double) bbox.bottom, expandedBottom); - if ((overlapRight - overlapLeft) > 4.0 && (overlapBottom - overlapTop) > 4.0) { - return true; - } - } - - double x = inst->x; - double y = inst->y; - return x >= expandedLeft && x <= expandedRight && - y >= expandedTop && y <= expandedBottom; -} + + double marginX = 24.0; + double marginY = 24.0; + double expandedLeft = left - marginX; + double expandedRight = right + marginX; + double expandedTop = top - marginY; + double expandedBottom = bottom + marginY; + + InstanceBBox bbox = Collision_computeBBox(runner->dataWin, inst); + if (bbox.valid) { + double overlapLeft = fmax((double) bbox.left, expandedLeft); + double overlapRight = fmin((double) bbox.right, expandedRight); + double overlapTop = fmax((double) bbox.top, expandedTop); + double overlapBottom = fmin((double) bbox.bottom, expandedBottom); + if ((overlapRight - overlapLeft) > 4.0 && (overlapBottom - overlapTop) > 4.0) { + return true; + } + } + + double x = inst->x; + double y = inst->y; + return x >= expandedLeft && x <= expandedRight && + y >= expandedTop && y <= expandedBottom; +} static bool Runner_shouldCull3DSBottomBattleFieldInstance(Runner* runner, Instance* inst) { if (runner == NULL || inst == NULL) return false; @@ -360,17 +360,26 @@ static bool Runner_is3DSBattleBackdropInstance(Runner* runner, Instance* inst) { return sprite->width >= 320 || sprite->height >= 240; } -static bool Runner_shouldOffset3DSTopBattleInstance(Runner* runner, Instance* inst) { - if (runner == NULL || inst == NULL) return false; - Runner_prepare3DSDrawBattleState(runner); - if (!runner->n3dsDrawBattleActive) return false; - if (Runner_is3DSAsrielBattle(runner)) return false; - if (Runner_shouldHideOn3DSTopScreen(runner, inst)) return false; - if (Runner_is3DSBattleBackdropInstance(runner, inst)) return false; - if (Runner_is3DSTopEnemyDialogueObject(runner, inst)) return false; - if (Runner_is3DSBattleWriterObjectIndex(runner, inst->objectIndex)) return false; - return true; -} +static bool Runner_shouldOffset3DSTopBattleInstance(Runner* runner, Instance* inst) { + if (runner == NULL || inst == NULL) return false; + Runner_prepare3DSDrawBattleState(runner); + if (!runner->n3dsDrawBattleActive) return false; + if (Runner_is3DSAsrielBattle(runner)) return false; + if (Runner_shouldHideOn3DSTopScreen(runner, inst)) return false; + if (Runner_is3DSBattleBackdropInstance(runner, inst)) return false; + if (Runner_is3DSTopEnemyDialogueObject(runner, inst)) return false; + if (Runner_is3DSBattleWriterObjectIndex(runner, inst->objectIndex)) return false; + return true; +} + +static float Runner_get3DSTopBattleInstanceYOffset(Runner* runner, Instance* inst) { + if (inst == NULL) return 0.0f; + if (Runner_objectContains3DSTokenInHierarchy(runner, inst->objectIndex, "asgoreb") || + Runner_objectContains3DSTokenInHierarchy(runner, inst->objectIndex, "asgorespear")) { + return -200.0f; + } + return k3DSTopBattleEnemyInstanceYOffset; +} static bool Runner_is3DSBattleUIObject(Runner* runner, Instance* inst) { if (runner == NULL || inst == NULL) return false; @@ -419,6 +428,30 @@ static bool Runner_is3DSBattleUIObject(Runner* runner, Instance* inst) { } } + if (Runner_objectContains3DSTokenInHierarchy(runner, inst->objectIndex, "blt") || + Runner_objectMatches3DSNameInHierarchy(runner, inst->objectIndex, "obj_asgore_spearswipe") || + Runner_objectMatches3DSNameInHierarchy(runner, inst->objectIndex, "obj_asgore_spearswipegen") || + Runner_objectMatches3DSNameInHierarchy(runner, inst->objectIndex, "obj_asgorebulparent") || + Runner_objectMatches3DSNameInHierarchy(runner, inst->objectIndex, "obj_asgoreattackgen") || + Runner_objectMatches3DSNameInHierarchy(runner, inst->objectIndex, "obj_asgore_firehit") || + Runner_objectMatches3DSNameInHierarchy(runner, inst->objectIndex, "obj_asgorefakespear") || + Runner_objectContains3DSTokenInHierarchy(runner, inst->objectIndex, "menubone") || + Runner_objectContains3DSTokenInHierarchy(runner, inst->objectIndex, "bonestab") || + Runner_objectContains3DSTokenInHierarchy(runner, inst->objectIndex, "bonewall") || + Runner_objectContains3DSTokenInHierarchy(runner, inst->objectIndex, "boneplat") || + Runner_objectContains3DSTokenInHierarchy(runner, inst->objectIndex, "boneloop") || + Runner_objectContains3DSTokenInHierarchy(runner, inst->objectIndex, "sans_bonebul") || + Runner_objectContains3DSTokenInHierarchy(runner, inst->objectIndex, "discoball") || + Runner_objectContains3DSTokenInHierarchy(runner, inst->objectIndex, "legline") || + Runner_objectContains3DSTokenInHierarchy(runner, inst->objectIndex, "sidedam") || + Runner_objectContains3DSTokenInHierarchy(runner, inst->objectIndex, "woshspiral") || + Runner_objectContains3DSTokenInHierarchy(runner, inst->objectIndex, "blackbox_rewind") || + Runner_objectContains3DSTokenInHierarchy(runner, inst->objectIndex, "answernode") || + Runner_objectContains3DSTokenInHierarchy(runner, inst->objectIndex, "target") || + Runner_objectContains3DSTokenInHierarchy(runner, inst->objectIndex, "eyeflash")) { + return true; + } + if (runner->n3dsDrawDodgingBullets && Runner_is3DSInstanceInsideBattleField(runner, inst)) { return true; } @@ -426,7 +459,7 @@ static bool Runner_is3DSBattleUIObject(Runner* runner, Instance* inst) { return false; } -static bool Runner_is3DSBattleFieldObject(Runner* runner, Instance* inst) { +static bool Runner_is3DSBattleFieldObject(Runner* runner, Instance* inst) { if (runner == NULL || inst == NULL) return false; if (!Runner_is3DSValidObjectIndex(runner, inst->objectIndex)) return false; Runner_prepare3DSDrawBattleState(runner); @@ -467,43 +500,43 @@ static bool Runner_is3DSBattleFieldObject(Runner* runner, Instance* inst) { return true; } - return false; -} - -static bool Runner_is3DSBattleFieldBackLayerObject(Runner* runner, Instance* inst) { - if (runner == NULL || inst == NULL) return false; - if (!Runner_is3DSValidObjectIndex(runner, inst->objectIndex)) return false; - - if (Runner_objectMatches3DSNameInHierarchy(runner, inst->objectIndex, "obj_battlecontroller") || - Runner_objectMatches3DSNameInHierarchy(runner, inst->objectIndex, "obj_blackborderer") || - Runner_objectMatches3DSNameInHierarchy(runner, inst->objectIndex, "obj_uborder") || - Runner_objectMatches3DSNameInHierarchy(runner, inst->objectIndex, "obj_dborder") || - Runner_objectMatches3DSNameInHierarchy(runner, inst->objectIndex, "obj_lborder") || - Runner_objectMatches3DSNameInHierarchy(runner, inst->objectIndex, "obj_rborder")) { - return true; - } - - if (Runner_objectContains3DSTokenInHierarchy(runner, inst->objectIndex, "border")) { - return true; - } - - if (inst->spriteIndex >= 0 && (uint32_t) inst->spriteIndex < runner->dataWin->sprt.count) { - const char* spriteName = runner->dataWin->sprt.sprites[inst->spriteIndex].name; - if (Runner_stringContainsToken(spriteName, "border")) { - return true; - } - } - - return false; -} - -static bool Runner_is3DSAsrielBattle(Runner* runner) { - if (runner == NULL) return false; - Runner_prepare3DSDrawBattleState(runner); - return runner->n3dsDrawBattleActive && runner->n3dsDrawAsrielBattle; -} - -static bool Runner_is3DSUndyneBattle(Runner* runner) { + return false; +} + +static bool Runner_is3DSBattleFieldBackLayerObject(Runner* runner, Instance* inst) { + if (runner == NULL || inst == NULL) return false; + if (!Runner_is3DSValidObjectIndex(runner, inst->objectIndex)) return false; + + if (Runner_objectMatches3DSNameInHierarchy(runner, inst->objectIndex, "obj_battlecontroller") || + Runner_objectMatches3DSNameInHierarchy(runner, inst->objectIndex, "obj_blackborderer") || + Runner_objectMatches3DSNameInHierarchy(runner, inst->objectIndex, "obj_uborder") || + Runner_objectMatches3DSNameInHierarchy(runner, inst->objectIndex, "obj_dborder") || + Runner_objectMatches3DSNameInHierarchy(runner, inst->objectIndex, "obj_lborder") || + Runner_objectMatches3DSNameInHierarchy(runner, inst->objectIndex, "obj_rborder")) { + return true; + } + + if (Runner_objectContains3DSTokenInHierarchy(runner, inst->objectIndex, "border")) { + return true; + } + + if (inst->spriteIndex >= 0 && (uint32_t) inst->spriteIndex < runner->dataWin->sprt.count) { + const char* spriteName = runner->dataWin->sprt.sprites[inst->spriteIndex].name; + if (Runner_stringContainsToken(spriteName, "border")) { + return true; + } + } + + return false; +} + +static bool Runner_is3DSAsrielBattle(Runner* runner) { + if (runner == NULL) return false; + Runner_prepare3DSDrawBattleState(runner); + return runner->n3dsDrawBattleActive && runner->n3dsDrawAsrielBattle; +} + +static bool Runner_is3DSUndyneBattle(Runner* runner) { if (runner == NULL) return false; Runner_prepare3DSDrawBattleState(runner); if (!runner->n3dsDrawBattleActive) return false; @@ -531,7 +564,7 @@ static bool Runner_is3DSUndyneBattle(Runner* runner) { return false; } -static void Runner_prepare3DSBattleReplayLists(Runner* runner, Drawable* drawables, int32_t drawableCount) { +static void Runner_prepare3DSBattleReplayLists(Runner* runner, Drawable* drawables, int32_t drawableCount) { if (runner == NULL || runner->n3dsBattleReplayListsValid) return; arrsetlen(runner->n3dsBattleFieldInstances, 0); @@ -541,39 +574,39 @@ static void Runner_prepare3DSBattleReplayLists(Runner* runner, Drawable* drawabl runner->n3dsBattleReplayListsValid = true; return; } - bool hasTopEnemyDialogue = Runner_has3DSTopEnemyDialogue(runner, drawables, drawableCount); - runner->n3dsDrawHasTopEnemyDialogue = hasTopEnemyDialogue; - - repeat(drawableCount, i) { - Drawable* d = &drawables[i]; + bool hasTopEnemyDialogue = Runner_has3DSTopEnemyDialogue(runner, drawables, drawableCount); + runner->n3dsDrawHasTopEnemyDialogue = hasTopEnemyDialogue; + + repeat(drawableCount, i) { + Drawable* d = &drawables[i]; if (d->type != DRAWABLE_INSTANCE) continue; - - Instance* inst = d->instance; - if (inst == NULL || !inst->active || !inst->visible) continue; - - if (Runner_is3DSBattleFieldObject(runner, inst) && - Runner_is3DSBattleFieldBackLayerObject(runner, inst)) { - arrput(runner->n3dsBattleFieldInstances, inst); - } - } - - repeat(drawableCount, i) { - Drawable* d = &drawables[i]; - if (d->type != DRAWABLE_INSTANCE) continue; - - Instance* inst = d->instance; - if (inst == NULL || !inst->active || !inst->visible) continue; - - bool isBattleFieldObject = Runner_is3DSBattleFieldObject(runner, inst); - bool isBattleUIObject = Runner_is3DSBattleUIObject(runner, inst); - if (isBattleFieldObject && !Runner_is3DSBattleFieldBackLayerObject(runner, inst)) { - arrput(runner->n3dsBattleFieldInstances, inst); - } - if (hasTopEnemyDialogue && Runner_is3DSBattleWriterObjectIndex(runner, inst->objectIndex)) { - isBattleUIObject = false; - } - if (isBattleUIObject && !isBattleFieldObject) arrput(runner->n3dsBattleUIInstances, inst); - } + + Instance* inst = d->instance; + if (inst == NULL || !inst->active || !inst->visible) continue; + + if (Runner_is3DSBattleFieldObject(runner, inst) && + Runner_is3DSBattleFieldBackLayerObject(runner, inst)) { + arrput(runner->n3dsBattleFieldInstances, inst); + } + } + + repeat(drawableCount, i) { + Drawable* d = &drawables[i]; + if (d->type != DRAWABLE_INSTANCE) continue; + + Instance* inst = d->instance; + if (inst == NULL || !inst->active || !inst->visible) continue; + + bool isBattleFieldObject = Runner_is3DSBattleFieldObject(runner, inst); + bool isBattleUIObject = Runner_is3DSBattleUIObject(runner, inst); + if (isBattleFieldObject && !Runner_is3DSBattleFieldBackLayerObject(runner, inst)) { + arrput(runner->n3dsBattleFieldInstances, inst); + } + if (hasTopEnemyDialogue && Runner_is3DSBattleWriterObjectIndex(runner, inst->objectIndex)) { + isBattleUIObject = false; + } + if (isBattleUIObject && !isBattleFieldObject) arrput(runner->n3dsBattleUIInstances, inst); + } runner->n3dsBattleReplayListsValid = true; } @@ -644,25 +677,25 @@ static N3DSResolvedDrawEvent* Runner_prepare3DSTopScreenGUIResponderList(Runner* return runner->n3dsTopScreenGUIResponderEvents[cacheIndex]; } -static void Runner_draw3DSBottomBattleUI(Runner* runner, Drawable* drawables, int32_t drawableCount, int32_t subtype, bool drawBattleFieldOnly, float scale, float yOffset) { - if (runner == NULL || runner->renderer == NULL) return; -#ifdef N3DS_DISABLE_BOTTOM_SCREEN - (void) drawables; - (void) drawableCount; - (void) subtype; - (void) drawBattleFieldOnly; - (void) scale; - (void) yOffset; - return; -#endif - int32_t slot = EventSlotMap_lookup(&runner->eventSlotMap, EVENT_DRAW, subtype); - if (subtype != DRAW_NORMAL && slot < 0) return; - if (Runner_is3DSAsrielBattle(runner)) return; - Runner_prepare3DSBattleReplayLists(runner, drawables, drawableCount); - if (!runner->n3dsDrawBattleActive) return; - Instance** instances = drawBattleFieldOnly ? runner->n3dsBattleFieldInstances : runner->n3dsBattleUIInstances; - int32_t instanceCount = (int32_t) arrlen(instances); - if (instanceCount <= 0) return; +static void Runner_draw3DSBottomBattleUI(Runner* runner, Drawable* drawables, int32_t drawableCount, int32_t subtype, bool drawBattleFieldOnly, float scale, float yOffset) { + if (runner == NULL || runner->renderer == NULL) return; +#ifdef N3DS_DISABLE_BOTTOM_SCREEN + (void) drawables; + (void) drawableCount; + (void) subtype; + (void) drawBattleFieldOnly; + (void) scale; + (void) yOffset; + return; +#endif + int32_t slot = EventSlotMap_lookup(&runner->eventSlotMap, EVENT_DRAW, subtype); + if (subtype != DRAW_NORMAL && slot < 0) return; + if (Runner_is3DSAsrielBattle(runner)) return; + Runner_prepare3DSBattleReplayLists(runner, drawables, drawableCount); + if (!runner->n3dsDrawBattleActive) return; + Instance** instances = drawBattleFieldOnly ? runner->n3dsBattleFieldInstances : runner->n3dsBattleUIInstances; + int32_t instanceCount = (int32_t) arrlen(instances); + if (instanceCount <= 0) return; int32_t guiW = runner->guiWidth > 0 ? runner->guiWidth : (int32_t) runner->dataWin->gen8.defaultWindowWidth; int32_t guiH = runner->guiHeight > 0 ? runner->guiHeight : (int32_t) runner->dataWin->gen8.defaultWindowHeight; @@ -673,13 +706,13 @@ static void Runner_draw3DSBottomBattleUI(Runner* runner, Drawable* drawables, in N3DSRenderer_beginBottomScreenGUI(runner->renderer, guiW, guiH); } else { N3DSRenderer_beginBottomScreenGUIEx(runner->renderer, guiW, guiH, scale, scale, 0.0f, yOffset); - } - repeat(instanceCount, i) { - Instance* inst = instances[i]; - if (inst == NULL || !inst->active || !inst->visible) continue; - if (drawBattleFieldOnly && subtype == DRAW_NORMAL && Runner_shouldCull3DSBottomBattleFieldInstance(runner, inst)) { - continue; - } + } + repeat(instanceCount, i) { + Instance* inst = instances[i]; + if (inst == NULL || !inst->active || !inst->visible) continue; + if (drawBattleFieldOnly && subtype == DRAW_NORMAL && Runner_shouldCull3DSBottomBattleFieldInstance(runner, inst)) { + continue; + } int32_t ownerObjectIndex = -1; int32_t codeId = slot >= 0 ? ResolvedEventTable_lookup(&runner->eventTable, inst->objectIndex, slot, &ownerObjectIndex) @@ -695,11 +728,11 @@ static void Runner_draw3DSBottomBattleUI(Runner* runner, Drawable* drawables, in N3DSRenderer_endBottomScreenGUI(runner->renderer); } -static bool Runner_shouldHideOn3DSTopScreen(Runner* runner, Instance* inst) { - if (runner == NULL || runner->renderer == NULL || inst == NULL) return false; - if (Runner_is3DSAsrielBattle(runner)) return false; - return Runner_is3DSBattleUIObject(runner, inst); -} +static bool Runner_shouldHideOn3DSTopScreen(Runner* runner, Instance* inst) { + if (runner == NULL || runner->renderer == NULL || inst == NULL) return false; + if (Runner_is3DSAsrielBattle(runner)) return false; + return Runner_is3DSBattleUIObject(runner, inst); +} #endif // ===[ Runtime Layer Teardown Helpers ]=== @@ -1437,11 +1470,11 @@ void Runner_drawTileLayer(Runner* runner, uint32_t layerIndex, RoomLayerTilesDat useCachedPath = cache->built && cache->cells != NULL && cache->rows != NULL; } -#if defined(__3DS__) && N3DS_ENABLE_TILE_LAYER_CHUNK_CACHE - if (useCachedPath && runner->renderer != NULL && runner->osType == OS_3DS) { - if (cache->n3dsChunkCacheId < 0 && runner->currentRoom != NULL) { - cache->n3dsChunkCacheId = N3DSRenderer_createTileLayerChunkCache( - runner->renderer, +#if defined(__3DS__) && N3DS_ENABLE_TILE_LAYER_CHUNK_CACHE + if (useCachedPath && runner->renderer != NULL && runner->osType == OS_3DS) { + if (cache->n3dsChunkCacheId < 0 && runner->currentRoom != NULL) { + cache->n3dsChunkCacheId = N3DSRenderer_createTileLayerChunkCache( + runner->renderer, runner->currentRoom->width, runner->currentRoom->height, cache @@ -1834,7 +1867,7 @@ void Runner_draw(Runner* runner) { Runner_shouldOffset3DSTopBattleInstance(runner, inst)) { offsetTopBattleEnemy = true; savedY = inst->y; - inst->y += k3DSTopBattleEnemyInstanceYOffset; + inst->y += Runner_get3DSTopBattleInstanceYOffset(runner, inst); } #endif if (codeId >= 0) { @@ -2025,19 +2058,19 @@ void Runner_draw(Runner* runner) { Runner_drawBackgrounds(runner, true); fireDrawSubtype(runner, drawables, drawableCount, DRAW_POST); -#ifdef __3DS__ - if (runner->renderer != NULL) { - if (Runner_is3DSBattleActive(runner)) { - bool isDodgingBullets = Runner_is3DSDodgingBullets(runner); - bool isAsrielBattle = Runner_is3DSAsrielBattle(runner); - bool isUndyneBattle = Runner_is3DSUndyneBattle(runner); - float battleFieldScale = isDodgingBullets ? ((isUndyneBattle || isAsrielBattle) ? 1.0f : k3DSBottomBattleFieldScale) : 1.0f; - float battleFieldYOffset = isDodgingBullets ? (isAsrielBattle ? 0.0f : k3DSBottomBattleFieldYOffset) : 0.0f; - Runner_draw3DSBottomBattleUI(runner, drawables, drawableCount, DRAW_NORMAL, true, battleFieldScale, battleFieldYOffset); - Runner_draw3DSBottomBattleUI(runner, drawables, drawableCount, DRAW_NORMAL, false, 1.0f, 0.0f); - } - } -#endif +#ifdef __3DS__ + if (runner->renderer != NULL) { + if (Runner_is3DSBattleActive(runner)) { + bool isDodgingBullets = Runner_is3DSDodgingBullets(runner); + bool isAsrielBattle = Runner_is3DSAsrielBattle(runner); + bool isUndyneBattle = Runner_is3DSUndyneBattle(runner); + float battleFieldScale = isDodgingBullets ? ((isUndyneBattle || isAsrielBattle) ? 1.0f : k3DSBottomBattleFieldScale) : 1.0f; + float battleFieldYOffset = isDodgingBullets ? (isAsrielBattle ? 0.0f : k3DSBottomBattleFieldYOffset) : 0.0f; + Runner_draw3DSBottomBattleUI(runner, drawables, drawableCount, DRAW_NORMAL, true, battleFieldScale, battleFieldYOffset); + Runner_draw3DSBottomBattleUI(runner, drawables, drawableCount, DRAW_NORMAL, false, 1.0f, 0.0f); + } + } +#endif if (traceDraw) { gRunnerDrawTraceCount++; } @@ -2056,58 +2089,58 @@ void Runner_drawGUI(Runner* runner) { fireDrawSubtype(runner, drawables, drawableCount, DRAW_GUI_BEGIN); runner->frameDrawGuiEventMs += Runner_nowMs() - phaseStartMs; #ifdef __3DS__ - if (runner->renderer != NULL) { - Runner_prepare3DSDrawBattleState(runner); - if (runner->n3dsDrawBattleActive) { - double battleReplayStartMs = Runner_nowMs(); - bool isDodgingBullets = runner->n3dsDrawDodgingBullets; - bool isAsrielBattle = Runner_is3DSAsrielBattle(runner); - bool isUndyneBattle = Runner_is3DSUndyneBattle(runner); - float battleFieldScale = isDodgingBullets ? ((isUndyneBattle || isAsrielBattle) ? 1.0f : k3DSBottomBattleFieldScale) : 1.0f; - float battleFieldYOffset = isDodgingBullets ? (isAsrielBattle ? 0.0f : k3DSBottomBattleFieldYOffset) : 0.0f; - Runner_draw3DSBottomBattleUI(runner, drawables, drawableCount, DRAW_GUI_BEGIN, true, battleFieldScale, battleFieldYOffset); - Runner_draw3DSBottomBattleUI(runner, drawables, drawableCount, DRAW_GUI_BEGIN, false, 1.0f, 0.0f); - runner->frameDrawBattleReplayMs += Runner_nowMs() - battleReplayStartMs; - } - } + if (runner->renderer != NULL) { + Runner_prepare3DSDrawBattleState(runner); + if (runner->n3dsDrawBattleActive) { + double battleReplayStartMs = Runner_nowMs(); + bool isDodgingBullets = runner->n3dsDrawDodgingBullets; + bool isAsrielBattle = Runner_is3DSAsrielBattle(runner); + bool isUndyneBattle = Runner_is3DSUndyneBattle(runner); + float battleFieldScale = isDodgingBullets ? ((isUndyneBattle || isAsrielBattle) ? 1.0f : k3DSBottomBattleFieldScale) : 1.0f; + float battleFieldYOffset = isDodgingBullets ? (isAsrielBattle ? 0.0f : k3DSBottomBattleFieldYOffset) : 0.0f; + Runner_draw3DSBottomBattleUI(runner, drawables, drawableCount, DRAW_GUI_BEGIN, true, battleFieldScale, battleFieldYOffset); + Runner_draw3DSBottomBattleUI(runner, drawables, drawableCount, DRAW_GUI_BEGIN, false, 1.0f, 0.0f); + runner->frameDrawBattleReplayMs += Runner_nowMs() - battleReplayStartMs; + } + } #endif phaseStartMs = Runner_nowMs(); fireDrawSubtype(runner, drawables, drawableCount, DRAW_GUI); runner->frameDrawGuiEventMs += Runner_nowMs() - phaseStartMs; #ifdef __3DS__ - if (runner->renderer != NULL) { - Runner_prepare3DSDrawBattleState(runner); - if (runner->n3dsDrawBattleActive) { - double battleReplayStartMs = Runner_nowMs(); - bool isDodgingBullets = runner->n3dsDrawDodgingBullets; - bool isAsrielBattle = Runner_is3DSAsrielBattle(runner); - bool isUndyneBattle = Runner_is3DSUndyneBattle(runner); - float battleFieldScale = isDodgingBullets ? ((isUndyneBattle || isAsrielBattle) ? 1.0f : k3DSBottomBattleFieldScale) : 1.0f; - float battleFieldYOffset = isDodgingBullets ? (isAsrielBattle ? 0.0f : k3DSBottomBattleFieldYOffset) : 0.0f; - Runner_draw3DSBottomBattleUI(runner, drawables, drawableCount, DRAW_GUI, true, battleFieldScale, battleFieldYOffset); - Runner_draw3DSBottomBattleUI(runner, drawables, drawableCount, DRAW_GUI, false, 1.0f, 0.0f); - runner->frameDrawBattleReplayMs += Runner_nowMs() - battleReplayStartMs; - } - } + if (runner->renderer != NULL) { + Runner_prepare3DSDrawBattleState(runner); + if (runner->n3dsDrawBattleActive) { + double battleReplayStartMs = Runner_nowMs(); + bool isDodgingBullets = runner->n3dsDrawDodgingBullets; + bool isAsrielBattle = Runner_is3DSAsrielBattle(runner); + bool isUndyneBattle = Runner_is3DSUndyneBattle(runner); + float battleFieldScale = isDodgingBullets ? ((isUndyneBattle || isAsrielBattle) ? 1.0f : k3DSBottomBattleFieldScale) : 1.0f; + float battleFieldYOffset = isDodgingBullets ? (isAsrielBattle ? 0.0f : k3DSBottomBattleFieldYOffset) : 0.0f; + Runner_draw3DSBottomBattleUI(runner, drawables, drawableCount, DRAW_GUI, true, battleFieldScale, battleFieldYOffset); + Runner_draw3DSBottomBattleUI(runner, drawables, drawableCount, DRAW_GUI, false, 1.0f, 0.0f); + runner->frameDrawBattleReplayMs += Runner_nowMs() - battleReplayStartMs; + } + } #endif phaseStartMs = Runner_nowMs(); fireDrawSubtype(runner, drawables, drawableCount, DRAW_GUI_END); runner->frameDrawGuiEventMs += Runner_nowMs() - phaseStartMs; #ifdef __3DS__ - if (runner->renderer != NULL) { - Runner_prepare3DSDrawBattleState(runner); - if (runner->n3dsDrawBattleActive) { - double battleReplayStartMs = Runner_nowMs(); - bool isDodgingBullets = runner->n3dsDrawDodgingBullets; - bool isAsrielBattle = Runner_is3DSAsrielBattle(runner); - bool isUndyneBattle = Runner_is3DSUndyneBattle(runner); - float battleFieldScale = isDodgingBullets ? ((isUndyneBattle || isAsrielBattle) ? 1.0f : k3DSBottomBattleFieldScale) : 1.0f; - float battleFieldYOffset = isDodgingBullets ? (isAsrielBattle ? 0.0f : k3DSBottomBattleFieldYOffset) : 0.0f; - Runner_draw3DSBottomBattleUI(runner, drawables, drawableCount, DRAW_GUI_END, true, battleFieldScale, battleFieldYOffset); - Runner_draw3DSBottomBattleUI(runner, drawables, drawableCount, DRAW_GUI_END, false, 1.0f, 0.0f); - runner->frameDrawBattleReplayMs += Runner_nowMs() - battleReplayStartMs; - } - } + if (runner->renderer != NULL) { + Runner_prepare3DSDrawBattleState(runner); + if (runner->n3dsDrawBattleActive) { + double battleReplayStartMs = Runner_nowMs(); + bool isDodgingBullets = runner->n3dsDrawDodgingBullets; + bool isAsrielBattle = Runner_is3DSAsrielBattle(runner); + bool isUndyneBattle = Runner_is3DSUndyneBattle(runner); + float battleFieldScale = isDodgingBullets ? ((isUndyneBattle || isAsrielBattle) ? 1.0f : k3DSBottomBattleFieldScale) : 1.0f; + float battleFieldYOffset = isDodgingBullets ? (isAsrielBattle ? 0.0f : k3DSBottomBattleFieldYOffset) : 0.0f; + Runner_draw3DSBottomBattleUI(runner, drawables, drawableCount, DRAW_GUI_END, true, battleFieldScale, battleFieldYOffset); + Runner_draw3DSBottomBattleUI(runner, drawables, drawableCount, DRAW_GUI_END, false, 1.0f, 0.0f); + runner->frameDrawBattleReplayMs += Runner_nowMs() - battleReplayStartMs; + } + } #endif } From fb3d5c68132ab722754817b060257a305abfb106 Mon Sep 17 00:00:00 2001 From: a huge sonic fan Date: Mon, 29 Jun 2026 09:42:46 -0700 Subject: [PATCH 2/4] inventory --- src/n3ds/n3ds_renderer.c | 7 ++ src/runner.c | 144 ++++++++++++++++++++++++++++++++++++++- src/runner.h | 25 ++++--- 3 files changed, 164 insertions(+), 12 deletions(-) diff --git a/src/n3ds/n3ds_renderer.c b/src/n3ds/n3ds_renderer.c index 427273e3..7ceba748 100644 --- a/src/n3ds/n3ds_renderer.c +++ b/src/n3ds/n3ds_renderer.c @@ -3483,6 +3483,13 @@ void N3DSRenderer_beginBottomScreenGUI(Renderer* base, int32_t guiW, int32_t gui N3DSRenderer_beginBottomScreenGUIEx(base, guiW, guiH, 1.0f, 1.0f, 0.0f, 0.0f); } +void N3DSRenderer_beginBottomScreenGUIView(Renderer* base, int32_t guiW, int32_t guiH, int32_t viewX, int32_t viewY) { + N3DSRenderer_beginBottomScreenGUIEx(base, guiW, guiH, 1.0f, 1.0f, 0.0f, 0.0f); + N3DSRenderer* renderer = (N3DSRenderer*) base; + renderer->viewX = viewX; + renderer->viewY = viewY; +} + void N3DSRenderer_endBottomScreenGUI(Renderer* base) { if (base == NULL) return; diff --git a/src/runner.c b/src/runner.c index 387bd5a7..7ab6c742 100644 --- a/src/runner.c +++ b/src/runner.c @@ -28,6 +28,7 @@ #ifdef __3DS__ void N3DSRenderer_beginBottomScreenGUIEx(Renderer* renderer, int32_t guiW, int32_t guiH, float scaleX, float scaleY, float offsetX, float offsetY); void N3DSRenderer_beginBottomScreenGUI(Renderer* renderer, int32_t guiW, int32_t guiH); +void N3DSRenderer_beginBottomScreenGUIView(Renderer* renderer, int32_t guiW, int32_t guiH, int32_t viewX, int32_t viewY); void N3DSRenderer_endBottomScreenGUI(Renderer* renderer); void N3DSRenderer_beginBottomScreenGUI2x(Renderer* renderer, int32_t guiW, int32_t guiH); void N3DSRenderer_endBottomScreenGUI2x(Renderer* renderer); @@ -107,6 +108,12 @@ static const float k3DSBottomBattleFieldScale = 1.0f; static const float k3DSBottomBattleFieldYOffset = 0.0f; static const float k3DSTopBattleEnemyInstanceYOffset = 112.0f; +// Bottom-screen text/dialogue/inventory UI toggle +// Set to 0 to render text boxes and inventory on the top screen (normal behavior) +#ifndef N3DS_ENABLE_BOTTOM_TEXT_UI +#define N3DS_ENABLE_BOTTOM_TEXT_UI 1 +#endif + static bool Runner_stringContainsToken(const char* haystack, const char* needle) { return haystack != NULL && needle != NULL && strstr(haystack, needle) != NULL; } @@ -116,6 +123,7 @@ static bool Runner_objectMatches3DSNameInHierarchy(Runner* runner, int32_t objec static bool Runner_objectContains3DSTokenInHierarchy(Runner* runner, int32_t objectIndex, const char* needle); static bool Runner_is3DSLiveBattleBorderObject(Runner* runner, Instance* inst); static bool Runner_shouldHideOn3DSTopScreen(Runner* runner, Instance* inst); +static bool Runner_is3DSBattleUIObject(Runner* runner, Instance* inst); static bool Runner_is3DSBattleBackdropInstance(Runner* runner, Instance* inst); static bool Runner_shouldOffset3DSTopBattleInstance(Runner* runner, Instance* inst); static bool Runner_is3DSBattleFieldObject(Runner* runner, Instance* inst); @@ -320,6 +328,80 @@ static bool Runner_is3DSBattleWriterObjectIndex(Runner* runner, int32_t objectIn Runner_objectMatches3DSNameInHierarchy(runner, objectIndex, "OBJ_NOMSCWRITER"); } +static bool Runner_is3DSTextUIObjectIndex(Runner* runner, int32_t objectIndex) { + if (!Runner_is3DSValidObjectIndex(runner, objectIndex)) return false; + // Writer objects (all variants inherit from obj_base_writer) + if (Runner_is3DSBattleWriterObjectIndex(runner, objectIndex)) return true; + if (Runner_objectMatches3DSNameInHierarchy(runner, objectIndex, "OBJ_INSTAWRITER")) return true; + // Dialogue and choice objects + if (Runner_objectMatches3DSNameInHierarchy(runner, objectIndex, "obj_dialoguer")) return true; + if (Runner_objectMatches3DSNameInHierarchy(runner, objectIndex, "obj_choicer")) return true; + // Damage number writers + if (Runner_objectContains3DSTokenInHierarchy(runner, objectIndex, "dmgwriter")) return true; + // Overworld game menu, inventory, and other UI + if (Runner_objectMatches3DSNameInHierarchy(runner, objectIndex, "obj_overworldcontroller")) return true; + if (Runner_objectMatches3DSNameInHierarchy(runner, objectIndex, "obj_gamemenu_fake")) return true; + if (Runner_objectMatches3DSNameInHierarchy(runner, objectIndex, "obj_itemswapper")) return true; + if (Runner_objectMatches3DSNameInHierarchy(runner, objectIndex, "obj_answernodule")) return true; + if (Runner_objectMatches3DSNameInHierarchy(runner, objectIndex, "obj_savepoint_fake")) return true; + if (Runner_objectContains3DSTokenInHierarchy(runner, objectIndex, "songwriter")) return true; + if (Runner_objectContains3DSTokenInHierarchy(runner, objectIndex, "FinalWriter")) return true; + if (Runner_objectContains3DSTokenInHierarchy(runner, objectIndex, "floweydmgwriter")) return true; + if (Runner_objectContains3DSTokenInHierarchy(runner, objectIndex, "mettatonnn_writer")) return true; + if (Runner_objectMatches3DSNameInHierarchy(runner, objectIndex, "obj_face")) return true; + return false; +} + +static bool Runner_is3DSTextUIObject(Runner* runner, Instance* inst) { + if (runner == NULL || inst == NULL) return false; + // If the object is already handled by the battle UI system, exclude it from text UI to prevent double-drawing + if (Runner_is3DSBattleUIObject(runner, inst)) return false; + return Runner_is3DSTextUIObjectIndex(runner, inst->objectIndex); +} + +static bool Runner_has3DSTextUI(Runner* runner) { + if (runner == NULL || runner->instances == NULL) return false; + int32_t instanceCount = (int32_t) arrlen(runner->instances); + repeat(instanceCount, i) { + Instance* inst = runner->instances[i]; + if (inst == NULL || inst->destroyed || !inst->active || !inst->visible) continue; + if (Runner_is3DSTextUIObject(runner, inst)) return true; + } + return false; +} + +static void Runner_prepare3DSTextUIState(Runner* runner) { + if (runner == NULL || runner->n3dsDrawTextUIStateValid) return; + runner->n3dsDrawTextUIActive = false; + if (!N3DS_ENABLE_BOTTOM_TEXT_UI) { runner->n3dsDrawTextUIStateValid = true; return; } + runner->n3dsDrawTextUIActive = Runner_has3DSTextUI(runner); + runner->n3dsDrawTextUIStateValid = true; +} + +static bool Runner_is3DSTextUIObjectCheck(Runner* runner, Instance* inst) { + if (!N3DS_ENABLE_BOTTOM_TEXT_UI) return false; + if (runner == NULL || inst == NULL) return false; + Runner_prepare3DSTextUIState(runner); + return runner->n3dsDrawTextUIActive && Runner_is3DSTextUIObject(runner, inst); +} + +static void Runner_prepare3DSTextUIList(Runner* runner, Drawable* drawables, int32_t drawableCount) { + if (runner == NULL || runner->n3dsTextUIListValid) return; + arrsetlen(runner->n3dsTextUIInstances, 0); + Runner_prepare3DSTextUIState(runner); + if (!runner->n3dsDrawTextUIActive) { runner->n3dsTextUIListValid = true; return; } + repeat(drawableCount, i) { + Drawable* d = &drawables[i]; + if (d->type != DRAWABLE_INSTANCE) continue; + Instance* inst = d->instance; + if (inst == NULL || !inst->active || !inst->visible) continue; + if (Runner_is3DSTextUIObject(runner, inst)) { + arrput(runner->n3dsTextUIInstances, inst); + } + } + runner->n3dsTextUIListValid = true; +} + static bool Runner_has3DSTopEnemyDialogue(Runner* runner, Drawable* drawables, int32_t drawableCount) { if (runner == NULL || drawables == NULL || drawableCount <= 0) return false; @@ -376,7 +458,7 @@ static float Runner_get3DSTopBattleInstanceYOffset(Runner* runner, Instance* ins if (inst == NULL) return 0.0f; if (Runner_objectContains3DSTokenInHierarchy(runner, inst->objectIndex, "asgoreb") || Runner_objectContains3DSTokenInHierarchy(runner, inst->objectIndex, "asgorespear")) { - return -200.0f; + return 0.0f; } return k3DSTopBattleEnemyInstanceYOffset; } @@ -728,10 +810,64 @@ static void Runner_draw3DSBottomBattleUI(Runner* runner, Drawable* drawables, in N3DSRenderer_endBottomScreenGUI(runner->renderer); } +static void Runner_draw3DSBottomTextUI(Runner* runner, Drawable* drawables, int32_t drawableCount, int32_t subtype, bool drawBattleFieldOnly, float scale, float yOffset) { + if (runner == NULL || runner->renderer == NULL) return; +#ifdef N3DS_DISABLE_BOTTOM_SCREEN + (void) drawables; + (void) drawableCount; + (void) subtype; + (void) drawBattleFieldOnly; + (void) scale; + (void) yOffset; + return; +#endif + if (!N3DS_ENABLE_BOTTOM_TEXT_UI) return; + (void) drawBattleFieldOnly; + int32_t slot = EventSlotMap_lookup(&runner->eventSlotMap, EVENT_DRAW, subtype); + if (subtype != DRAW_NORMAL && slot < 0) return; + Runner_prepare3DSTextUIList(runner, drawables, drawableCount); + if (!runner->n3dsDrawTextUIActive) return; + Instance** instances = runner->n3dsTextUIInstances; + int32_t instanceCount = (int32_t) arrlen(instances); + if (instanceCount <= 0) return; + + int32_t viewIndex = runner->viewCurrent; + int32_t guiW = runner->views[viewIndex].viewWidth; + int32_t guiH = runner->views[viewIndex].viewHeight; + if (guiW <= 0) guiW = 320; + if (guiH <= 0) guiH = 240; + int32_t viewX = runner->views[viewIndex].viewX; + int32_t viewY = runner->views[viewIndex].viewY; + + if (scale == 1.0f && yOffset == 0.0f) { + N3DSRenderer_beginBottomScreenGUIView(runner->renderer, guiW, guiH, viewX, viewY); + } else { + N3DSRenderer_beginBottomScreenGUIEx(runner->renderer, guiW, guiH, scale, scale, 0.0f, yOffset); + } + repeat(instanceCount, i) { + Instance* inst = instances[i]; + if (inst == NULL || !inst->active || !inst->visible) continue; + int32_t ownerObjectIndex = -1; + int32_t codeId = slot >= 0 + ? ResolvedEventTable_lookup(&runner->eventTable, inst->objectIndex, slot, &ownerObjectIndex) + : -1; + if (codeId >= 0) { + Runner_executeResolvedEvent(runner, inst, EVENT_DRAW, subtype, codeId, ownerObjectIndex); + } else { + if (subtype == DRAW_NORMAL) { + Renderer_drawSelf(runner->renderer, inst); + } + } + } + N3DSRenderer_endBottomScreenGUI(runner->renderer); +} + static bool Runner_shouldHideOn3DSTopScreen(Runner* runner, Instance* inst) { if (runner == NULL || runner->renderer == NULL || inst == NULL) return false; if (Runner_is3DSAsrielBattle(runner)) return false; - return Runner_is3DSBattleUIObject(runner, inst); + if (Runner_is3DSBattleUIObject(runner, inst)) return true; + if (Runner_is3DSTextUIObjectCheck(runner, inst)) return true; + return false; } #endif @@ -1854,6 +1990,7 @@ void Runner_draw(Runner* runner) { } n3dsTopScreenDrawInstanceCursor++; } + if (Runner_is3DSTextUIObjectCheck(runner, inst)) continue; #endif int32_t ownerObjectIndex = -1; int32_t codeId = drawNormalSlot >= 0 @@ -2069,6 +2206,7 @@ void Runner_draw(Runner* runner) { Runner_draw3DSBottomBattleUI(runner, drawables, drawableCount, DRAW_NORMAL, true, battleFieldScale, battleFieldYOffset); Runner_draw3DSBottomBattleUI(runner, drawables, drawableCount, DRAW_NORMAL, false, 1.0f, 0.0f); } + Runner_draw3DSBottomTextUI(runner, drawables, drawableCount, DRAW_NORMAL, false, 1.0f, 0.0f); } #endif if (traceDraw) { @@ -2179,6 +2317,8 @@ void Runner_drawViews(Runner* runner, int32_t gameW, int32_t gameH, float displa runner->n3dsDrawBattleStateValid = false; runner->n3dsBattleReplayListsValid = false; runner->n3dsTopScreenGUIListValid = false; + runner->n3dsDrawTextUIStateValid = false; + runner->n3dsTextUIListValid = false; repeat(3, i) runner->n3dsTopScreenGUIResponderListsValid[i] = false; bool viewsEnabled = (activeRoom->flags & 1) != 0; diff --git a/src/runner.h b/src/runner.h index 9323fcf4..e4edb3f8 100644 --- a/src/runner.h +++ b/src/runner.h @@ -472,16 +472,21 @@ typedef struct Runner { int32_t frameStepTopObjectIndex; double* frameStepObjectMsByObject; uint32_t* frameStepObjectCallsByObject; - bool n3dsDrawBattleStateValid; - bool n3dsDrawBattleActive; - bool n3dsDrawDodgingBullets; - bool n3dsDrawAsrielBattle; - bool n3dsDrawHasTopEnemyDialogue; - bool n3dsBattleReplayListsValid; - bool n3dsTopScreenGUIListValid; - Instance** n3dsBattleFieldInstances; - Instance** n3dsBattleUIInstances; - Instance** n3dsTopScreenGUIInstances; + bool n3dsDrawBattleStateValid; + bool n3dsDrawBattleActive; + bool n3dsDrawDodgingBullets; + bool n3dsDrawAsrielBattle; + bool n3dsDrawHasTopEnemyDialogue; + bool n3dsBattleReplayListsValid; + bool n3dsTopScreenGUIListValid; + Instance** n3dsBattleFieldInstances; + Instance** n3dsBattleUIInstances; + Instance** n3dsTopScreenGUIInstances; + // Bottom-screen text/dialogue/inventory UI state (separate from battle) + bool n3dsDrawTextUIStateValid; + bool n3dsDrawTextUIActive; + bool n3dsTextUIListValid; + Instance** n3dsTextUIInstances; bool n3dsTopScreenGUIResponderListsValid[3]; N3DSResolvedDrawEvent* n3dsTopScreenGUIResponderEvents[3]; From 3d625d5a1ff234007107dddf036b439cf17fa420 Mon Sep 17 00:00:00 2001 From: "casriel." Date: Mon, 29 Jun 2026 09:54:31 -0700 Subject: [PATCH 3/4] No no no! --- src/runner.c | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/src/runner.c b/src/runner.c index 7ab6c742..bec0e606 100644 --- a/src/runner.c +++ b/src/runner.c @@ -109,7 +109,7 @@ static const float k3DSBottomBattleFieldYOffset = 0.0f; static const float k3DSTopBattleEnemyInstanceYOffset = 112.0f; // Bottom-screen text/dialogue/inventory UI toggle -// Set to 0 to render text boxes and inventory on the top screen (normal behavior) +// Set to 0 to render text boxes on top.. or turn it off in runner menu lmao #ifndef N3DS_ENABLE_BOTTOM_TEXT_UI #define N3DS_ENABLE_BOTTOM_TEXT_UI 1 #endif @@ -330,14 +330,12 @@ static bool Runner_is3DSBattleWriterObjectIndex(Runner* runner, int32_t objectIn static bool Runner_is3DSTextUIObjectIndex(Runner* runner, int32_t objectIndex) { if (!Runner_is3DSValidObjectIndex(runner, objectIndex)) return false; - // Writer objects (all variants inherit from obj_base_writer) + // Writer objects (all variants inherit from obj_base_writer.. a future issue with other games maybe) if (Runner_is3DSBattleWriterObjectIndex(runner, objectIndex)) return true; if (Runner_objectMatches3DSNameInHierarchy(runner, objectIndex, "OBJ_INSTAWRITER")) return true; - // Dialogue and choice objects + // Dialogue and choice objects.. this maps ALL text in the game so other games that use the same variables will have an issue uh oh if (Runner_objectMatches3DSNameInHierarchy(runner, objectIndex, "obj_dialoguer")) return true; if (Runner_objectMatches3DSNameInHierarchy(runner, objectIndex, "obj_choicer")) return true; - // Damage number writers - if (Runner_objectContains3DSTokenInHierarchy(runner, objectIndex, "dmgwriter")) return true; // Overworld game menu, inventory, and other UI if (Runner_objectMatches3DSNameInHierarchy(runner, objectIndex, "obj_overworldcontroller")) return true; if (Runner_objectMatches3DSNameInHierarchy(runner, objectIndex, "obj_gamemenu_fake")) return true; @@ -345,8 +343,6 @@ static bool Runner_is3DSTextUIObjectIndex(Runner* runner, int32_t objectIndex) { if (Runner_objectMatches3DSNameInHierarchy(runner, objectIndex, "obj_answernodule")) return true; if (Runner_objectMatches3DSNameInHierarchy(runner, objectIndex, "obj_savepoint_fake")) return true; if (Runner_objectContains3DSTokenInHierarchy(runner, objectIndex, "songwriter")) return true; - if (Runner_objectContains3DSTokenInHierarchy(runner, objectIndex, "FinalWriter")) return true; - if (Runner_objectContains3DSTokenInHierarchy(runner, objectIndex, "floweydmgwriter")) return true; if (Runner_objectContains3DSTokenInHierarchy(runner, objectIndex, "mettatonnn_writer")) return true; if (Runner_objectMatches3DSNameInHierarchy(runner, objectIndex, "obj_face")) return true; return false; From ceb37a8acbd7c1db20ece46574c24c50497d0d79 Mon Sep 17 00:00:00 2001 From: TheOliveOli <152160964+TheOliveOli38@users.noreply.github.com> Date: Thu, 2 Jul 2026 17:40:40 +0200 Subject: [PATCH 4/4] add required wii u package dependencies for building to readme --- README.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 53d866a4..d48f053f 100644 --- a/README.md +++ b/README.md @@ -63,9 +63,9 @@ Of course, there are exceptions that break game compatibility altogether: ## Building For Wii U -You must have a proper devkitPro Wii U enviroment set up and configured for your platform. +You must have a proper devkitPro Wii U enviroment set up and configured for your platform. The `wiiu-sdl2` and `ppc-bzip2` devkitPro packages also need to be installed. -On Windows, make sure MinGW is located in your system PATH (C:/MinGW/bin) before proceeding with build instructions +On Windows, make sure MinGW is located in your system PATH (C:/MinGW/bin) before proceeding with build instructions. Configure with the Wii U CMake wrapper and then build: @@ -95,7 +95,7 @@ On Windows, build from PowerShell with: You must have a proper devkitPro 3DS environment set up and configured for your platform. -On Windows, make sure MinGW is located in your system PATH (C:/MinGW/bin) before proceeding with build instructions +On Windows, make sure MinGW is located in your system PATH (C:/MinGW/bin) before proceeding with build instructions. Configure and build it with the devkitPro 3DS toolchain: