Dungeon core
Source: src/dungeon.c · 1708 lines · 70 functions.
Module responsibility
Owns the game state machine, player movement, room events, AI route selection, controls, screen updates and drawing.
Module entry points
These functions are exposed to the rest of the Dungeon system. Open one to follow its decisions and helper calls.
How to read these diagrams
Each function has an expandable structural diagram derived from the current C source. Decisions split into True and False, loop bodies are indented, and arrows show statement order.
These are source diagrams before preprocessing, not runtime traces. Callback execution, task scheduling and macro expansion are not inferred.
Functions in this file
traceDungeonCreation() · source line 84
static void traceDungeonCreation(const char *stage, int error, int errno_value)
- Execute
(void)stage; - Execute
(void)error; - Execute
(void)errno_value;
Read original function source
static void traceDungeonCreation(const char *stage, int error, int errno_value)
{
/* Temporary creation diagnostics retained for future SPIFFS investigation.
snprintf(creation_trace, sizeof(creation_trace), "%s E%d N%d", stage, error, errno_value);
ESP_LOGI("dungeon", "CREATE %s error=%d errno=%d", stage, error, errno_value);
*/
(void)stage;
(void)error;
(void)errno_value;
}startPlayTimer() · source line 95
static void startPlayTimer(uint32_t now_ms)
- Execute
last_play_time_ms = now_ms; - Execute
play_timer_running = true;
Read original function source
static void startPlayTimer(uint32_t now_ms)
{
last_play_time_ms = now_ms;
play_timer_running = true;
}commitPlayTime() · source line 101
static void commitPlayTime(uint32_t now_ms)
Calls into this documentation:
- Decision
!play_timer_runningTrue- Exit this function
return;
FalseContinue after this decision.
- Execute
const uint32_t elapsed = now_ms - last_play_time_ms; - Execute
const uint32_t elapsed_seconds = elapsed / 1000U; - Decision
elapsed_seconds == 0UTrue- Exit this function
return;
FalseContinue after this decision.
- Execute
DungeonPlayerState *player = getDungeonPlayerState(); - Execute
player->timePlayedSeconds = UINT32_MAX - player->timePlayedSeconds < elapsed_seconds ? UINT32_MAX : player->timePlayedSeconds + elapsed_seconds; - Execute
last_play_time_ms += elapsed_seconds * 1000U;
Read original function source
static void commitPlayTime(uint32_t now_ms)
{
if (!play_timer_running) return;
const uint32_t elapsed = now_ms - last_play_time_ms;
const uint32_t elapsed_seconds = elapsed / 1000U;
if (elapsed_seconds == 0U) return;
DungeonPlayerState *player = getDungeonPlayerState();
player->timePlayedSeconds = UINT32_MAX - player->timePlayedSeconds < elapsed_seconds
? UINT32_MAX : player->timePlayedSeconds + elapsed_seconds;
last_play_time_ms += elapsed_seconds * 1000U;
}prepareEntranceImage() · source line 116
static bool prepareEntranceImage(void)
Calls into this documentation:
- Execute
DungeonWorld *world = getDungeonWorld(); - Execute
const DungeonCell *cell = &world->floors[world->entrance.floor].cells[world->entrance.y][world->entrance.x]; - Exit this function
return resolveDungeonTilePath(cell, tile_path, sizeof(tile_path));
Read original function source
static bool prepareEntranceImage(void)
{
DungeonWorld *world = getDungeonWorld();
const DungeonCell *cell = &world->floors[world->entrance.floor].cells[world->entrance.y][world->entrance.x];
return resolveDungeonTilePath(cell, tile_path, sizeof(tile_path));
}prepareStartBackground() · source line 123
static void prepareStartBackground(void)
- Execute
const int written = snprintf(tile_path, sizeof(tile_path), "%s", DUNGEON_START_BACKGROUND_PATH); - Execute
image_visible = false; - Execute
decode_pending = written > 0 && (size_t)written < sizeof(tile_path);
Read original function source
static void prepareStartBackground(void)
{
const int written = snprintf(tile_path, sizeof(tile_path), "%s", DUNGEON_START_BACKGROUND_PATH);
image_visible = false;
decode_pending = written > 0 && (size_t)written < sizeof(tile_path);
}prepareRoomImage() · source line 130
static bool prepareRoomImage(const DungeonPosition *position)
Calls into this documentation:
- Execute
const DungeonCell *cell = &getDungeonWorld()->floors[position->floor].cells[position->y][position->x]; - Exit this function
return resolveDungeonTilePath(cell, tile_path, sizeof(tile_path));
Read original function source
static bool prepareRoomImage(const DungeonPosition *position)
{
const DungeonCell *cell = &getDungeonWorld()->floors[position->floor].cells[position->y][position->x];
return resolveDungeonTilePath(cell, tile_path, sizeof(tile_path));
}recordExplored() · source line 136
static void recordExplored(const DungeonPosition *position)
Calls into this documentation:
- Execute
markDungeonExplored(position, &explored_result);
Read original function source
static void recordExplored(const DungeonPosition *position)
{
markDungeonExplored(position, &explored_result);
}isEncounterRoom() · source line 141
static bool isEncounterRoom(const DungeonCell *cell)
- Exit this function
return cell->tileType == DUNGEON_TILE_ROOM_OPEN || cell->tileType == DUNGEON_TILE_DEAD_END_ROOM_ROUND || cell->tileType == DUNGEON_TILE_DEAD_END_ROOM_SQUARE || cell->tileType == DUNGEON_TILE_DEAD_END_ROOM_ROUND_ALT || cell->tileType == DUNGEON_TILE_EXIT;
Read original function source
static bool isEncounterRoom(const DungeonCell *cell)
{
return cell->tileType == DUNGEON_TILE_ROOM_OPEN || cell->tileType == DUNGEON_TILE_DEAD_END_ROOM_ROUND ||
cell->tileType == DUNGEON_TILE_DEAD_END_ROOM_SQUARE || cell->tileType == DUNGEON_TILE_DEAD_END_ROOM_ROUND_ALT ||
cell->tileType == DUNGEON_TILE_EXIT;
}isShopRoom() · source line 148
static bool isShopRoom(const DungeonCell *cell)
- Exit this function
return cell->tileType == DUNGEON_TILE_STAIRS_UP || cell->tileType == DUNGEON_TILE_STAIRS_DOWN || cell->tileType == DUNGEON_TILE_STAIRS_BOTH;
Read original function source
static bool isShopRoom(const DungeonCell *cell)
{
return cell->tileType == DUNGEON_TILE_STAIRS_UP || cell->tileType == DUNGEON_TILE_STAIRS_DOWN || cell->tileType == DUNGEON_TILE_STAIRS_BOTH;
}applyMoveRecovery() · source line 153
static void applyMoveRecovery(DungeonPlayerState *player)
Calls into this documentation:
- Execute
const DungeonConfig *config = getDungeonConfig(); - Decision
config->healthRegenMoves == 0U || player->moves % config->healthRegenMoves != 0UTrue- Exit this function
return;
FalseContinue after this decision.
- Execute
const uint16_t limit = (uint16_t)(((uint32_t)player->maxHealth * config->healthRegenLimitPercent + 99U) / 100U); - Decision
player->health < limitTrue- Execute
const uint32_t recovered = (uint32_t)player->health + config->healthRegenAmount; - Execute
player->health = recovered > limit ? limit : (uint16_t)recovered;
FalseContinue after this decision.
Read original function source
static void applyMoveRecovery(DungeonPlayerState *player)
{
const DungeonConfig *config = getDungeonConfig();
if (config->healthRegenMoves == 0U || player->moves % config->healthRegenMoves != 0U) return;
const uint16_t limit = (uint16_t)(((uint32_t)player->maxHealth * config->healthRegenLimitPercent + 99U) / 100U);
if (player->health < limit) {
const uint32_t recovered = (uint32_t)player->health + config->healthRegenAmount;
player->health = recovered > limit ? limit : (uint16_t)recovered;
}
}encounterEnemyType() · source line 164
static uint8_t encounterEnemyType(const DungeonCell *cell)
Calls into this documentation:
- Execute
const uint8_t saved_type = (uint8_t)((cell->eventFlags & DUNGEON_ENEMY_DETAIL_MASK) >> DUNGEON_ENEMY_DETAIL_SHIFT); - Decision
saved_type != DUNGEON_ENEMY_NONETrue- Exit this function
return saved_type;
FalseContinue after this decision.
- Execute
const DungeonConfig *config = getDungeonConfig(); - Exit this function
return player_position.floor == 0U ? config->floor1EnemyMinLevel : player_position.floor == 1U ? config->floor2EnemyMinLevel : config->floor3EnemyMinLevel;
Read original function source
static uint8_t encounterEnemyType(const DungeonCell *cell)
{
const uint8_t saved_type = (uint8_t)((cell->eventFlags & DUNGEON_ENEMY_DETAIL_MASK) >> DUNGEON_ENEMY_DETAIL_SHIFT);
if (saved_type != DUNGEON_ENEMY_NONE) return saved_type;
const DungeonConfig *config = getDungeonConfig();
return player_position.floor == 0U ? config->floor1EnemyMinLevel : player_position.floor == 1U ? config->floor2EnemyMinLevel : config->floor3EnemyMinLevel;
}prepareEventImage() · source line 175
static void prepareEventImage(const char *prefix, uint8_t variant)
- Execute
const unsigned image_variant = variant == 0U ? 1U : variant; - Execute
const int written = snprintf(tile_path, sizeof(tile_path), DUNGEON_TILE_ROOT "/t_m/%s%02u.jpg", prefix, image_variant); - Execute
image_visible = false; - Execute
decode_pending = written > 0 && (size_t)written < sizeof(tile_path);
Read original function source
static void prepareEventImage(const char *prefix, uint8_t variant)
{
const unsigned image_variant = variant == 0U ? 1U : variant;
const int written = snprintf(tile_path, sizeof(tile_path), DUNGEON_TILE_ROOT "/t_m/%s%02u.jpg", prefix, image_variant);
image_visible = false;
decode_pending = written > 0 && (size_t)written < sizeof(tile_path);
}prepareRewardImage() · source line 183
static void prepareRewardImage(const DungeonCell *cell, DungeonTreasureType treasure)
Calls into this documentation:
- Execute
const char *prefix = reward_is_key ? "t_key_" : treasure == DUNGEON_TREASURE_HEALTH ? "t_health_" : treasure == DUNGEON_TREASURE_LIFE ? "t_life_" : treasure == DUNGEON_TREASURE_ATTACK ? "t_att_" : treasure == DUNGEON_TREASURE_DEFENCE ? "t_def_" : "t_gold_"; - Execute
prepareEventImage(prefix, cell->variant);
Read original function source
static void prepareRewardImage(const DungeonCell *cell, DungeonTreasureType treasure)
{
const char *prefix = reward_is_key ? "t_key_" : treasure == DUNGEON_TREASURE_HEALTH ? "t_health_" :
treasure == DUNGEON_TREASURE_LIFE ? "t_life_" : treasure == DUNGEON_TREASURE_ATTACK ? "t_att_" :
treasure == DUNGEON_TREASURE_DEFENCE ? "t_def_" : "t_gold_";
prepareEventImage(prefix, cell->variant);
}resolveRoomTrap() · source line 194
static bool resolveRoomTrap(void)
Calls into this documentation:
- Execute
DungeonCell *cell = &getDungeonWorld()->floors[player_position.floor].cells[player_position.y][player_position.x]; - Execute
DungeonPlayerState *player = getDungeonPlayerState(); - Execute
const DungeonPlayerState previous_player = *player; - Execute
const uint16_t previous_flags = cell->stateFlags; - Execute
const DungeonConfig *config = getDungeonConfig(); - Execute
uint16_t minimum = player_position.floor == 0U ? config->floor1TrapDamageMin : player_position.floor == 1U ? config->floor2TrapDamageMin : config->floor3TrapDamageMin; - Execute
uint16_t maximum = player_position.floor == 0U ? config->floor1TrapDamageMax : player_position.floor == 1U ? config->floor2TrapDamageMax : config->floor3TrapDamageMax; - Execute
const uint32_t rolled_damage = minimum + esp_random() % ((uint32_t)maximum - minimum + 1U); - Execute
const uint32_t reduced_damage = rolled_damage > player->defence ? rolled_damage - player->defence : config->combatMinimumDamage; - Execute
trap_damage = (uint16_t)(reduced_damage < config->combatMinimumDamage ? config->combatMinimumDamage : reduced_damage); - Execute
player->health = trap_damage >= player->health ? 0U : (uint16_t)(player->health - trap_damage); - Execute
cell->stateFlags |= DUNGEON_STATE_TRIGGERED; - Decision
player->health == 0UTrue- Execute
DungeonPosition respawn = getDungeonWorld()->entrance; - Decision
player_position.floor > 0UTrue- Execute
respawn = getDungeonWorld()->stairLinks[player_position.floor - 1U].lower;
FalseContinue after this decision.
- Execute
player->position = respawn; - Execute
player->health = (uint16_t)(player->maxHealth / 2U + player->maxHealth % 2U); - Execute
player_position = respawn; - Execute
trap_defeated = true;
False- Execute
trap_defeated = false;
- Decision
!saveDungeonWorld(&storage_result) || !saveDungeonPlayer(&player_result)True- Execute
*player = previous_player; - Execute
player_position = previous_player.position; - Execute
cell->stateFlags = previous_flags; - Execute
saveDungeonWorld(&storage_result); - Exit this function
return false;
FalseContinue after this decision.
- Execute
recordExplored(&player_position); - Execute
state = DUNGEON_TRAP_RESULT; - Execute
prepareEventImage("t_trap_", cell->variant); - Exit this function
return true;
Read original function source
static bool resolveRoomTrap(void)
{
DungeonCell *cell = &getDungeonWorld()->floors[player_position.floor].cells[player_position.y][player_position.x];
DungeonPlayerState *player = getDungeonPlayerState();
const DungeonPlayerState previous_player = *player;
const uint16_t previous_flags = cell->stateFlags;
const DungeonConfig *config = getDungeonConfig();
uint16_t minimum = player_position.floor == 0U ? config->floor1TrapDamageMin : player_position.floor == 1U ? config->floor2TrapDamageMin : config->floor3TrapDamageMin;
uint16_t maximum = player_position.floor == 0U ? config->floor1TrapDamageMax : player_position.floor == 1U ? config->floor2TrapDamageMax : config->floor3TrapDamageMax;
const uint32_t rolled_damage = minimum + esp_random() % ((uint32_t)maximum - minimum + 1U);
const uint32_t reduced_damage = rolled_damage > player->defence ? rolled_damage - player->defence : config->combatMinimumDamage;
trap_damage = (uint16_t)(reduced_damage < config->combatMinimumDamage ? config->combatMinimumDamage : reduced_damage);
player->health = trap_damage >= player->health ? 0U : (uint16_t)(player->health - trap_damage);
cell->stateFlags |= DUNGEON_STATE_TRIGGERED;
if (player->health == 0U) {
DungeonPosition respawn = getDungeonWorld()->entrance;
if (player_position.floor > 0U) respawn = getDungeonWorld()->stairLinks[player_position.floor - 1U].lower;
player->position = respawn;
player->health = (uint16_t)(player->maxHealth / 2U + player->maxHealth % 2U);
player_position = respawn;
trap_defeated = true;
} else trap_defeated = false;
if (!saveDungeonWorld(&storage_result) || !saveDungeonPlayer(&player_result)) {
*player = previous_player;
player_position = previous_player.position;
cell->stateFlags = previous_flags;
saveDungeonWorld(&storage_result);
return false;
}
recordExplored(&player_position);
state = DUNGEON_TRAP_RESULT;
prepareEventImage("t_trap_", cell->variant);
return true;
}startRoomEvent() · source line 229
static bool startRoomEvent(const DungeonPosition *source)
Calls into this documentation:
- Execute
const DungeonCell *cell = &getDungeonWorld()->floors[player_position.floor].cells[player_position.y][player_position.x]; - Decision
(cell->eventFlags & DUNGEON_EVENT_TRAP) != 0U && (cell->stateFlags & DUNGEON_STATE_TRIGGERED) == 0UTrue- Exit this function
return resolveRoomTrap();
FalseContinue after this decision.
- Decision
(cell->eventFlags & DUNGEON_EVENT_REQUIRES_KEY) != 0U && (cell->stateFlags & DUNGEON_STATE_UNLOCKED) == 0U && getDungeonPlayerState()->keys == 0UTrue- Decision
encounterEnemyType(cell) == DUNGEON_ENEMY_DRAGONTrue- Execute
DungeonPlayerState *player = getDungeonPlayerState(); - Execute
const uint32_t previous_flags = player->runFlags; - Execute
player->runFlags |= DUNGEON_RUN_KEY_REVEALED; - Decision
!saveDungeonPlayer(&player_result)True- Execute
player->runFlags = previous_flags; - Execute
state = DUNGEON_NOTICE; - Exit this function
return true;
FalseContinue after this decision.
FalseContinue after this decision.
- Execute
encounter_source = *source; - Execute
state = DUNGEON_LOCKED_DOOR; - Execute
image_visible = false; - Execute
decode_pending = false; - Exit this function
return true;
FalseContinue after this decision.
- Decision
isEncounterRoom(cell) && (cell->eventFlags & DUNGEON_EVENT_ENCOUNTER) != 0U && (cell->stateFlags & DUNGEON_STATE_COMPLETED) == 0UTrue- Execute
const uint8_t enemy_type = encounterEnemyType(cell); - Decision
!startDungeonCombat(enemy_type, &combat_state)True- Exit this function
return false;
FalseContinue after this decision.
- Execute
encounter_source = *source; - Execute
selected_combat_option = COMBAT_ATTACK; - Execute
combat_text[0] = '\0'; - Execute
combat_victory = false; - Execute
combat_result_title = NULL; - Execute
state = DUNGEON_COMBAT; - Execute
prepareEventImage(combat_state.enemyType == DUNGEON_ENEMY_LEVEL_1 ? "e_lvl1_" : combat_state.enemyType == DUNGEON_ENEMY_LEVEL_2 ? "e_lvl2_" : combat_state.enemyType == DUNGEON_ENEMY_LEVEL_3 ? "e_lvl3_" : combat_state.enemyType == DUNGEON_ENEMY_LEVEL_4 ? "e_lvl4_" : "e_drag_", cell->variant); - Exit this function
return true;
FalseContinue after this decision.
- Exit this function
return collectRoomReward();
Read original function source
static bool startRoomEvent(const DungeonPosition *source)
{
const DungeonCell *cell = &getDungeonWorld()->floors[player_position.floor].cells[player_position.y][player_position.x];
if ((cell->eventFlags & DUNGEON_EVENT_TRAP) != 0U && (cell->stateFlags & DUNGEON_STATE_TRIGGERED) == 0U) return resolveRoomTrap();
if ((cell->eventFlags & DUNGEON_EVENT_REQUIRES_KEY) != 0U && (cell->stateFlags & DUNGEON_STATE_UNLOCKED) == 0U && getDungeonPlayerState()->keys == 0U) {
if (encounterEnemyType(cell) == DUNGEON_ENEMY_DRAGON) {
DungeonPlayerState *player = getDungeonPlayerState();
const uint32_t previous_flags = player->runFlags;
player->runFlags |= DUNGEON_RUN_KEY_REVEALED;
if (!saveDungeonPlayer(&player_result)) {
player->runFlags = previous_flags;
state = DUNGEON_NOTICE;
return true;
}
}
encounter_source = *source;
state = DUNGEON_LOCKED_DOOR;
image_visible = false;
decode_pending = false;
return true;
}
if (isEncounterRoom(cell) && (cell->eventFlags & DUNGEON_EVENT_ENCOUNTER) != 0U && (cell->stateFlags & DUNGEON_STATE_COMPLETED) == 0U) {
const uint8_t enemy_type = encounterEnemyType(cell);
if (!startDungeonCombat(enemy_type, &combat_state)) return false;
encounter_source = *source;
selected_combat_option = COMBAT_ATTACK;
combat_text[0] = '\0';
combat_victory = false;
combat_result_title = NULL;
state = DUNGEON_COMBAT;
prepareEventImage(combat_state.enemyType == DUNGEON_ENEMY_LEVEL_1 ? "e_lvl1_" : combat_state.enemyType == DUNGEON_ENEMY_LEVEL_2 ? "e_lvl2_" :
combat_state.enemyType == DUNGEON_ENEMY_LEVEL_3 ? "e_lvl3_" : combat_state.enemyType == DUNGEON_ENEMY_LEVEL_4 ? "e_lvl4_" : "e_drag_", cell->variant);
return true;
}
return collectRoomReward();
}collectRoomReward() · source line 266
static bool collectRoomReward(void)
Calls into this documentation:
- Execute
DungeonCell *cell = &getDungeonWorld()->floors[player_position.floor].cells[player_position.y][player_position.x]; - Execute
DungeonPlayerState *player = getDungeonPlayerState(); - Execute
const DungeonPlayerState previous_player = *player; - Execute
const uint16_t previous_flags = cell->stateFlags; - Execute
DungeonTreasureType treasure = DUNGEON_TREASURE_GOLD; - Execute
bool pending = false; - Execute
bool collected = false; - Execute
reward_text[0] = '\0'; - Execute
reward_is_key = false; - Decision
(cell->eventFlags & DUNGEON_EVENT_KEY) != 0U && (cell->stateFlags & DUNGEON_STATE_KEY_COLLECTED) == 0UTrue- Decision
player->keys < UINT16_MAXTrue- Execute
++player->keys;
FalseContinue after this decision.
- Execute
cell->stateFlags |= DUNGEON_STATE_KEY_COLLECTED; - Execute
snprintf(reward_text, sizeof(reward_text), "KEY COLLECTED"); - Execute
reward_is_key = true; - Execute
pending = true; - Execute
collected = true;
False- Decision
(cell->eventFlags & DUNGEON_EVENT_TREASURE) != 0U && (cell->stateFlags & DUNGEON_STATE_OPENED) == 0UTrue- Execute
const DungeonConfig *config = getDungeonConfig(); - Execute
treasure = (DungeonTreasureType)((cell->eventFlags & DUNGEON_TREASURE_DETAIL_MASK) >> DUNGEON_TREASURE_DETAIL_SHIFT); - Execute
pending = true; - Decision
treasure == DUNGEON_TREASURE_HEALTHTrue- Decision
player->health < player->maxHealthTrue- Execute
player->health = player->maxHealth; - Execute
snprintf(reward_text, sizeof(reward_text), "HEALTH RESTORED"); - Execute
collected = true;
False- Decision
player->healthPotions < config->maxHealthPotionsTrue- Execute
++player->healthPotions; - Execute
snprintf(reward_text, sizeof(reward_text), "POTION +1"); - Execute
collected = true;
False- Execute
snprintf(reward_text, sizeof(reward_text), "CANNOT CARRY");
False- Decision
treasure == DUNGEON_TREASURE_LIFETrue- Execute
player->maxHealth = player->maxHealth <= UINT16_MAX - config->rareLifeHealthGain ? player->maxHealth + config->rareLifeHealthGain : UINT16_MAX; - Execute
snprintf(reward_text, sizeof(reward_text), "MAX HEALTH +%u", (unsigned)config->rareLifeHealthGain); - Execute
collected = true;
False- Decision
treasure == DUNGEON_TREASURE_ATTACKTrue- Execute
player->attack = player->attack <= UINT16_MAX - config->rareAttackGain ? player->attack + config->rareAttackGain : UINT16_MAX; - Execute
snprintf(reward_text, sizeof(reward_text), "ATTACK +%u", (unsigned)config->rareAttackGain); - Execute
collected = true;
False- Decision
treasure == DUNGEON_TREASURE_DEFENCETrue- Execute
player->defence = player->defence <= UINT16_MAX - config->rareDefenceGain ? player->defence + config->rareDefenceGain : UINT16_MAX; - Execute
snprintf(reward_text, sizeof(reward_text), "DEFENCE +%u", (unsigned)config->rareDefenceGain); - Execute
collected = true;
False- Execute
uint16_t minimum = player_position.floor == 0U ? config->floor1GoldMin : player_position.floor == 1U ? config->floor2GoldMin : config->floor3GoldMin; - Execute
uint16_t maximum = player_position.floor == 0U ? config->floor1GoldMax : player_position.floor == 1U ? config->floor2GoldMax : config->floor3GoldMax; - Execute
const uint32_t amount = minimum + (esp_random() % ((uint32_t)maximum - minimum + 1U)); - Execute
player->gold = player->gold <= UINT32_MAX - amount ? player->gold + amount : UINT32_MAX; - Execute
snprintf(reward_text, sizeof(reward_text), "GOLD +%lu", (unsigned long)amount); - Execute
collected = true;
- Decision
collectedTrue- Execute
cell->stateFlags |= DUNGEON_STATE_OPENED;
FalseContinue after this decision.
FalseContinue after this decision.
- Decision
!pendingTrue- Exit this function
return false;
FalseContinue after this decision.
- Decision
!collectedTrue- Execute
state = DUNGEON_REWARD; - Execute
prepareRewardImage(cell, treasure); - Exit this function
return true;
FalseContinue after this decision.
- Decision
!saveDungeonWorld(&storage_result) || !saveDungeonPlayer(&player_result)True- Execute
*player = previous_player; - Execute
cell->stateFlags = previous_flags; - Execute
saveDungeonWorld(&storage_result); - Exit this function
return false;
FalseContinue after this decision.
- Execute
state = DUNGEON_REWARD; - Execute
prepareRewardImage(cell, treasure); - Exit this function
return true;
Read original function source
static bool collectRoomReward(void)
{
DungeonCell *cell = &getDungeonWorld()->floors[player_position.floor].cells[player_position.y][player_position.x];
DungeonPlayerState *player = getDungeonPlayerState();
const DungeonPlayerState previous_player = *player;
const uint16_t previous_flags = cell->stateFlags;
DungeonTreasureType treasure = DUNGEON_TREASURE_GOLD;
bool pending = false;
bool collected = false;
reward_text[0] = '\0';
reward_is_key = false;
if ((cell->eventFlags & DUNGEON_EVENT_KEY) != 0U && (cell->stateFlags & DUNGEON_STATE_KEY_COLLECTED) == 0U) {
if (player->keys < UINT16_MAX) ++player->keys;
cell->stateFlags |= DUNGEON_STATE_KEY_COLLECTED;
snprintf(reward_text, sizeof(reward_text), "KEY COLLECTED");
reward_is_key = true;
pending = true;
collected = true;
} else if ((cell->eventFlags & DUNGEON_EVENT_TREASURE) != 0U && (cell->stateFlags & DUNGEON_STATE_OPENED) == 0U) {
const DungeonConfig *config = getDungeonConfig();
treasure = (DungeonTreasureType)((cell->eventFlags & DUNGEON_TREASURE_DETAIL_MASK) >> DUNGEON_TREASURE_DETAIL_SHIFT);
pending = true;
if (treasure == DUNGEON_TREASURE_HEALTH) {
if (player->health < player->maxHealth) {
player->health = player->maxHealth;
snprintf(reward_text, sizeof(reward_text), "HEALTH RESTORED");
collected = true;
} else if (player->healthPotions < config->maxHealthPotions) {
++player->healthPotions;
snprintf(reward_text, sizeof(reward_text), "POTION +1");
collected = true;
} else snprintf(reward_text, sizeof(reward_text), "CANNOT CARRY");
} else if (treasure == DUNGEON_TREASURE_LIFE) {
player->maxHealth = player->maxHealth <= UINT16_MAX - config->rareLifeHealthGain ? player->maxHealth + config->rareLifeHealthGain : UINT16_MAX;
snprintf(reward_text, sizeof(reward_text), "MAX HEALTH +%u", (unsigned)config->rareLifeHealthGain);
collected = true;
} else if (treasure == DUNGEON_TREASURE_ATTACK) {
player->attack = player->attack <= UINT16_MAX - config->rareAttackGain ? player->attack + config->rareAttackGain : UINT16_MAX;
snprintf(reward_text, sizeof(reward_text), "ATTACK +%u", (unsigned)config->rareAttackGain);
collected = true;
} else if (treasure == DUNGEON_TREASURE_DEFENCE) {
player->defence = player->defence <= UINT16_MAX - config->rareDefenceGain ? player->defence + config->rareDefenceGain : UINT16_MAX;
snprintf(reward_text, sizeof(reward_text), "DEFENCE +%u", (unsigned)config->rareDefenceGain);
collected = true;
} else {
uint16_t minimum = player_position.floor == 0U ? config->floor1GoldMin : player_position.floor == 1U ? config->floor2GoldMin : config->floor3GoldMin;
uint16_t maximum = player_position.floor == 0U ? config->floor1GoldMax : player_position.floor == 1U ? config->floor2GoldMax : config->floor3GoldMax;
const uint32_t amount = minimum + (esp_random() % ((uint32_t)maximum - minimum + 1U));
player->gold = player->gold <= UINT32_MAX - amount ? player->gold + amount : UINT32_MAX;
snprintf(reward_text, sizeof(reward_text), "GOLD +%lu", (unsigned long)amount);
collected = true;
}
if (collected) cell->stateFlags |= DUNGEON_STATE_OPENED;
}
if (!pending) return false;
if (!collected) {
state = DUNGEON_REWARD;
prepareRewardImage(cell, treasure);
return true;
}
if (!saveDungeonWorld(&storage_result) || !saveDungeonPlayer(&player_result)) {
*player = previous_player;
cell->stateFlags = previous_flags;
saveDungeonWorld(&storage_result);
return false;
}
state = DUNGEON_REWARD;
prepareRewardImage(cell, treasure);
return true;
}movePlayer() · source line 339
static bool movePlayer(uint8_t exit)
Calls into this documentation:
- Execute
const DungeonCell *current = &getDungeonWorld()->floors[player_position.floor].cells[player_position.y][player_position.x]; - Decision
(current->exits & exit) == 0UTrue- Exit this function
return false;
FalseContinue after this decision.
- Execute
DungeonPosition destination = player_position; - Decision
exit == DUNGEON_EXIT_NORTH && destination.y > 0UTrue- Execute
--destination.y;
False- Decision
exit == DUNGEON_EXIT_EAST && destination.x + 1U < DUNGEON_WIDTHTrue- Execute
++destination.x;
False- Decision
exit == DUNGEON_EXIT_SOUTH && destination.y + 1U < DUNGEON_HEIGHTTrue- Execute
++destination.y;
False- Decision
exit == DUNGEON_EXIT_WEST && destination.x > 0UTrue- Execute
--destination.x;
False- Exit this function
return false;
- Execute
const DungeonCell *cell = &getDungeonWorld()->floors[destination.floor].cells[destination.y][destination.x]; - Decision
!resolveDungeonTilePath(cell, tile_path, sizeof(tile_path))True- Exit this function
return false;
FalseContinue after this decision.
- Execute
DungeonPlayerState *player = getDungeonPlayerState(); - Execute
const DungeonPlayerState previous_player = *player; - Execute
player->position = destination; - Execute
++player->moves; - Execute
applyMoveRecovery(player); - Decision
!saveDungeonPlayer(&player_result)True- Execute
*player = previous_player; - Exit this function
return false;
FalseContinue after this decision.
- Execute
player_position = destination; - Execute
recordExplored(&player_position); - Decision
startRoomEvent(&previous_player.position)True- Exit this function
return true;
FalseContinue after this decision.
- Execute
image_visible = false; - Execute
decode_pending = true; - Exit this function
return true;
Read original function source
static bool movePlayer(uint8_t exit)
{
const DungeonCell *current = &getDungeonWorld()->floors[player_position.floor].cells[player_position.y][player_position.x];
if ((current->exits & exit) == 0U) return false;
DungeonPosition destination = player_position;
if (exit == DUNGEON_EXIT_NORTH && destination.y > 0U) --destination.y;
else if (exit == DUNGEON_EXIT_EAST && destination.x + 1U < DUNGEON_WIDTH) ++destination.x;
else if (exit == DUNGEON_EXIT_SOUTH && destination.y + 1U < DUNGEON_HEIGHT) ++destination.y;
else if (exit == DUNGEON_EXIT_WEST && destination.x > 0U) --destination.x;
else return false;
const DungeonCell *cell = &getDungeonWorld()->floors[destination.floor].cells[destination.y][destination.x];
if (!resolveDungeonTilePath(cell, tile_path, sizeof(tile_path))) return false;
DungeonPlayerState *player = getDungeonPlayerState();
const DungeonPlayerState previous_player = *player;
player->position = destination;
++player->moves;
applyMoveRecovery(player);
if (!saveDungeonPlayer(&player_result)) {
*player = previous_player;
return false;
}
player_position = destination;
recordExplored(&player_position);
if (startRoomEvent(&previous_player.position)) return true;
image_visible = false;
decode_pending = true;
return true;
}moveStairs() · source line 371
static bool moveStairs(bool up)
Calls into this documentation:
- Execute
DungeonWorld *world = getDungeonWorld(); - Execute
const DungeonCell *current = &world->floors[player_position.floor].cells[player_position.y][player_position.x]; - Decision
(up && current->tileType != DUNGEON_TILE_STAIRS_UP && current->tileType != DUNGEON_TILE_STAIRS_BOTH) || (!up && current->tileType != DUNGEON_TILE_STAIRS_DOWN && current->tileType != DUNGEON_TILE_STAIRS_BOTH)True- Exit this function
return false;
FalseContinue after this decision.
- Execute
DungeonPosition destination; - Execute
bool found = false; - FOR loop
unsigned index = 0; index < DUNGEON_FLOOR_COUNT - 1U; ++indexRepeat body while the loop condition permits; then continue below.
- Execute
const DungeonStairLink *link = &world->stairLinks[index]; - Decision
player_position.floor == link->upper.floor && player_position.x == link->upper.x && player_position.y == link->upper.yTrue- Execute
destination = link->lower; - Execute
found = true; - Exit enclosing loop or switch
break;
FalseContinue after this decision.
- Decision
player_position.floor == link->lower.floor && player_position.x == link->lower.x && player_position.y == link->lower.yTrue- Execute
destination = link->upper; - Execute
found = true; - Exit enclosing loop or switch
break;
FalseContinue after this decision.
- Decision
!foundTrue- Exit this function
return false;
FalseContinue after this decision.
- Execute
const DungeonCell *cell = &world->floors[destination.floor].cells[destination.y][destination.x]; - Decision
!resolveDungeonTilePath(cell, tile_path, sizeof(tile_path))True- Exit this function
return false;
FalseContinue after this decision.
- Execute
DungeonPlayerState *player = getDungeonPlayerState(); - Execute
const DungeonPlayerState previous_player = *player; - Execute
player->position = destination; - Execute
++player->moves; - Execute
applyMoveRecovery(player); - Decision
!saveDungeonPlayer(&player_result)True- Execute
*player = previous_player; - Exit this function
return false;
FalseContinue after this decision.
- Execute
player_position = destination; - Execute
recordExplored(&player_position); - Decision
startRoomEvent(&previous_player.position)True- Exit this function
return true;
FalseContinue after this decision.
- Execute
image_visible = false; - Execute
decode_pending = true; - Exit this function
return true;
Read original function source
static bool moveStairs(bool up)
{
DungeonWorld *world = getDungeonWorld();
const DungeonCell *current = &world->floors[player_position.floor].cells[player_position.y][player_position.x];
if ((up && current->tileType != DUNGEON_TILE_STAIRS_UP && current->tileType != DUNGEON_TILE_STAIRS_BOTH) ||
(!up && current->tileType != DUNGEON_TILE_STAIRS_DOWN && current->tileType != DUNGEON_TILE_STAIRS_BOTH)) return false;
DungeonPosition destination;
bool found = false;
for (unsigned index = 0; index < DUNGEON_FLOOR_COUNT - 1U; ++index) {
const DungeonStairLink *link = &world->stairLinks[index];
if (player_position.floor == link->upper.floor &&
player_position.x == link->upper.x && player_position.y == link->upper.y) {
destination = link->lower;
found = true;
break;
}
if (player_position.floor == link->lower.floor &&
player_position.x == link->lower.x && player_position.y == link->lower.y) {
destination = link->upper;
found = true;
break;
}
}
if (!found) return false;
const DungeonCell *cell = &world->floors[destination.floor].cells[destination.y][destination.x];
if (!resolveDungeonTilePath(cell, tile_path, sizeof(tile_path))) return false;
DungeonPlayerState *player = getDungeonPlayerState();
const DungeonPlayerState previous_player = *player;
player->position = destination;
++player->moves;
applyMoveRecovery(player);
if (!saveDungeonPlayer(&player_result)) {
*player = previous_player;
return false;
}
player_position = destination;
recordExplored(&player_position);
if (startRoomEvent(&previous_player.position)) return true;
image_visible = false;
decode_pending = true;
return true;
}stairAvailable() · source line 416
static bool stairAvailable(bool up)
Calls into this documentation:
- Execute
DungeonWorld *world = getDungeonWorld(); - Execute
const DungeonCell *current = &world->floors[player_position.floor].cells[player_position.y][player_position.x]; - Decision
(up && current->tileType != DUNGEON_TILE_STAIRS_UP && current->tileType != DUNGEON_TILE_STAIRS_BOTH) || (!up && current->tileType != DUNGEON_TILE_STAIRS_DOWN && current->tileType != DUNGEON_TILE_STAIRS_BOTH)True- Exit this function
return false;
FalseContinue after this decision.
- FOR loop
unsigned index = 0; index < DUNGEON_FLOOR_COUNT - 1U; ++indexRepeat body while the loop condition permits; then continue below.
- Execute
const DungeonStairLink *link = &world->stairLinks[index]; - Decision
player_position.floor == link->upper.floor && player_position.x == link->upper.x && player_position.y == link->upper.yTrue- Exit this function
return true;
FalseContinue after this decision.
- Decision
player_position.floor == link->lower.floor && player_position.x == link->lower.x && player_position.y == link->lower.yTrue- Exit this function
return true;
FalseContinue after this decision.
- Exit this function
return false;
Read original function source
static bool stairAvailable(bool up)
{
DungeonWorld *world = getDungeonWorld();
const DungeonCell *current = &world->floors[player_position.floor].cells[player_position.y][player_position.x];
if ((up && current->tileType != DUNGEON_TILE_STAIRS_UP && current->tileType != DUNGEON_TILE_STAIRS_BOTH) ||
(!up && current->tileType != DUNGEON_TILE_STAIRS_DOWN && current->tileType != DUNGEON_TILE_STAIRS_BOTH)) return false;
for (unsigned index = 0; index < DUNGEON_FLOOR_COUNT - 1U; ++index) {
const DungeonStairLink *link = &world->stairLinks[index];
if (player_position.floor == link->upper.floor &&
player_position.x == link->upper.x && player_position.y == link->upper.y) return true;
if (player_position.floor == link->lower.floor &&
player_position.x == link->lower.x && player_position.y == link->lower.y) return true;
}
return false;
}aiPositionIndex() · source line 434
static uint16_t aiPositionIndex(DungeonPosition position)
- Exit this function
return (uint16_t)(((uint16_t)position.floor * DUNGEON_WIDTH * DUNGEON_HEIGHT) + ((uint16_t)position.y * DUNGEON_WIDTH) + position.x);
Read original function source
static uint16_t aiPositionIndex(DungeonPosition position)
{
return (uint16_t)(((uint16_t)position.floor * DUNGEON_WIDTH * DUNGEON_HEIGHT) +
((uint16_t)position.y * DUNGEON_WIDTH) + position.x);
}aiSamePosition() · source line 440
static bool aiSamePosition(DungeonPosition left, DungeonPosition right)
- Exit this function
return left.floor == right.floor && left.x == right.x && left.y == right.y;
Read original function source
static bool aiSamePosition(DungeonPosition left, DungeonPosition right)
{
return left.floor == right.floor && left.x == right.x && left.y == right.y;
}aiNeighbour() · source line 445
static bool aiNeighbour(DungeonPosition position, ai_option_t option, DungeonPosition *destination)
Calls into this documentation:
- Execute
DungeonWorld *world = getDungeonWorld(); - Execute
*destination = position; - Decision
option == AI_UP || option == AI_DOWNTrue- FOR loop
uint8_t index = 0U; index < DUNGEON_FLOOR_COUNT - 1U; ++indexRepeat body while the loop condition permits; then continue below.
- Execute
const DungeonStairLink *link = &world->stairLinks[index]; - Decision
option == AI_DOWN && aiSamePosition(position, link->upper)True- Execute
*destination = link->lower; - Exit this function
return true;
FalseContinue after this decision.
- Decision
option == AI_UP && aiSamePosition(position, link->lower)True- Execute
*destination = link->upper; - Exit this function
return true;
FalseContinue after this decision.
- Exit this function
return false;
FalseContinue after this decision.
- Execute
const uint8_t exits[] = { DUNGEON_EXIT_NORTH, DUNGEON_EXIT_EAST, DUNGEON_EXIT_SOUTH, DUNGEON_EXIT_WEST }; - Execute
const DungeonCell *cell = &world->floors[position.floor].cells[position.y][position.x]; - Decision
(cell->exits & exits[option]) == 0UTrue- Exit this function
return false;
FalseContinue after this decision.
- Decision
option == AI_NORTH && position.y > 0UTrue- Execute
--destination->y;
False- Decision
option == AI_EAST && position.x + 1U < DUNGEON_WIDTHTrue- Execute
++destination->x;
False- Decision
option == AI_SOUTH && position.y + 1U < DUNGEON_HEIGHTTrue- Execute
++destination->y;
False- Decision
option == AI_WEST && position.x > 0UTrue- Execute
--destination->x;
False- Exit this function
return false;
- Exit this function
return true;
Read original function source
static bool aiNeighbour(DungeonPosition position, ai_option_t option, DungeonPosition *destination)
{
DungeonWorld *world = getDungeonWorld();
*destination = position;
if (option == AI_UP || option == AI_DOWN) {
for (uint8_t index = 0U; index < DUNGEON_FLOOR_COUNT - 1U; ++index) {
const DungeonStairLink *link = &world->stairLinks[index];
if (option == AI_DOWN && aiSamePosition(position, link->upper)) { *destination = link->lower; return true; }
if (option == AI_UP && aiSamePosition(position, link->lower)) { *destination = link->upper; return true; }
}
return false;
}
const uint8_t exits[] = { DUNGEON_EXIT_NORTH, DUNGEON_EXIT_EAST, DUNGEON_EXIT_SOUTH, DUNGEON_EXIT_WEST };
const DungeonCell *cell = &world->floors[position.floor].cells[position.y][position.x];
if ((cell->exits & exits[option]) == 0U) return false;
if (option == AI_NORTH && position.y > 0U) --destination->y;
else if (option == AI_EAST && position.x + 1U < DUNGEON_WIDTH) ++destination->x;
else if (option == AI_SOUTH && position.y + 1U < DUNGEON_HEIGHT) ++destination->y;
else if (option == AI_WEST && position.x > 0U) --destination->x;
else return false;
return true;
}aiFindPath() · source line 468
static bool aiFindPath(DungeonPosition target, ai_option_t *first_option)
Calls into this documentation:
- Execute
const DungeonPosition start = player_position; - Decision
aiSamePosition(start, target)True- Exit this function
return false;
FalseContinue after this decision.
- Execute
memset(ai_route_first_option, 0xFF, sizeof(ai_route_first_option)); - Execute
uint16_t head = 0U, tail = 0U; - Execute
const uint16_t start_index = aiPositionIndex(start); - Execute
ai_route_queue[tail++] = start_index; - Execute
ai_route_first_option[start_index] = 0xFEU; - WHILE loop
head < tailRepeat body while the loop condition permits; then continue below.
- Execute
const uint16_t current_index = ai_route_queue[head++]; - Execute
DungeonPosition current = { (uint8_t)(current_index / (DUNGEON_WIDTH * DUNGEON_HEIGHT)), (uint8_t)(current_index % DUNGEON_WIDTH), (uint8_t)((current_index % (DUNGEON_WIDTH * DUNGEON_HEIGHT)) / DUNGEON_WIDTH) }; - FOR loop
uint8_t option = AI_NORTH; option <= AI_DOWN; ++optionRepeat body while the loop condition permits; then continue below.
- Execute
DungeonPosition next; - Decision
!aiNeighbour(current, (ai_option_t)option, &next)True- Next loop iteration
continue;
FalseContinue after this decision.
- Execute
const uint16_t next_index = aiPositionIndex(next); - Decision
ai_route_first_option[next_index] != 0xFFUTrue- Next loop iteration
continue;
FalseContinue after this decision.
- Execute
ai_route_first_option[next_index] = current_index == start_index ? option : ai_route_first_option[current_index]; - Decision
aiSamePosition(next, target)True- Execute
*first_option = (ai_option_t)ai_route_first_option[next_index]; - Exit this function
return true;
FalseContinue after this decision.
- Execute
ai_route_queue[tail++] = next_index;
- Exit this function
return false;
Read original function source
static bool aiFindPath(DungeonPosition target, ai_option_t *first_option)
{
const DungeonPosition start = player_position;
if (aiSamePosition(start, target)) return false;
memset(ai_route_first_option, 0xFF, sizeof(ai_route_first_option));
uint16_t head = 0U, tail = 0U;
const uint16_t start_index = aiPositionIndex(start);
ai_route_queue[tail++] = start_index;
ai_route_first_option[start_index] = 0xFEU;
while (head < tail) {
const uint16_t current_index = ai_route_queue[head++];
DungeonPosition current = { (uint8_t)(current_index / (DUNGEON_WIDTH * DUNGEON_HEIGHT)),
(uint8_t)(current_index % DUNGEON_WIDTH),
(uint8_t)((current_index % (DUNGEON_WIDTH * DUNGEON_HEIGHT)) / DUNGEON_WIDTH) };
for (uint8_t option = AI_NORTH; option <= AI_DOWN; ++option) {
DungeonPosition next;
if (!aiNeighbour(current, (ai_option_t)option, &next)) continue;
const uint16_t next_index = aiPositionIndex(next);
if (ai_route_first_option[next_index] != 0xFFU) continue;
ai_route_first_option[next_index] = current_index == start_index ? option : ai_route_first_option[current_index];
if (aiSamePosition(next, target)) { *first_option = (ai_option_t)ai_route_first_option[next_index]; return true; }
ai_route_queue[tail++] = next_index;
}
}
return false;
}aiNearestUnexplored() · source line 495
static bool aiNearestUnexplored(ai_option_t *first_option)
Calls into this documentation:
- Execute
const DungeonPosition start = player_position; - Execute
memset(ai_route_first_option, 0xFF, sizeof(ai_route_first_option)); - Execute
uint16_t head = 0U, tail = 0U; - Execute
const uint16_t start_index = aiPositionIndex(start); - Execute
ai_route_queue[tail++] = start_index; - Execute
ai_route_first_option[start_index] = 0xFEU; - WHILE loop
head < tailRepeat body while the loop condition permits; then continue below.
- Execute
const uint16_t current_index = ai_route_queue[head++]; - Execute
DungeonPosition current = { (uint8_t)(current_index / (DUNGEON_WIDTH * DUNGEON_HEIGHT)), (uint8_t)(current_index % DUNGEON_WIDTH), (uint8_t)((current_index % (DUNGEON_WIDTH * DUNGEON_HEIGHT)) / DUNGEON_WIDTH) }; - FOR loop
uint8_t option = AI_NORTH; option <= AI_DOWN; ++optionRepeat body while the loop condition permits; then continue below.
- Execute
DungeonPosition next; - Decision
!aiNeighbour(current, (ai_option_t)option, &next)True- Next loop iteration
continue;
FalseContinue after this decision.
- Execute
const uint16_t next_index = aiPositionIndex(next); - Decision
ai_route_first_option[next_index] != 0xFFUTrue- Next loop iteration
continue;
FalseContinue after this decision.
- Execute
ai_route_first_option[next_index] = current_index == start_index ? option : ai_route_first_option[current_index]; - Decision
!isDungeonExplored(next.floor, next.x, next.y)True- Execute
*first_option = (ai_option_t)ai_route_first_option[next_index]; - Exit this function
return true;
FalseContinue after this decision.
- Execute
ai_route_queue[tail++] = next_index;
- Exit this function
return false;
Read original function source
static bool aiNearestUnexplored(ai_option_t *first_option)
{
const DungeonPosition start = player_position;
memset(ai_route_first_option, 0xFF, sizeof(ai_route_first_option));
uint16_t head = 0U, tail = 0U;
const uint16_t start_index = aiPositionIndex(start);
ai_route_queue[tail++] = start_index;
ai_route_first_option[start_index] = 0xFEU;
while (head < tail) {
const uint16_t current_index = ai_route_queue[head++];
DungeonPosition current = { (uint8_t)(current_index / (DUNGEON_WIDTH * DUNGEON_HEIGHT)),
(uint8_t)(current_index % DUNGEON_WIDTH),
(uint8_t)((current_index % (DUNGEON_WIDTH * DUNGEON_HEIGHT)) / DUNGEON_WIDTH) };
for (uint8_t option = AI_NORTH; option <= AI_DOWN; ++option) {
DungeonPosition next;
if (!aiNeighbour(current, (ai_option_t)option, &next)) continue;
const uint16_t next_index = aiPositionIndex(next);
if (ai_route_first_option[next_index] != 0xFFU) continue;
ai_route_first_option[next_index] = current_index == start_index ? option : ai_route_first_option[current_index];
if (!isDungeonExplored(next.floor, next.x, next.y)) { *first_option = (ai_option_t)ai_route_first_option[next_index]; return true; }
ai_route_queue[tail++] = next_index;
}
}
return false;
}aiFindUncollectedKey() · source line 521
static bool aiFindUncollectedKey(DungeonPosition *position)
Calls into this documentation:
- Execute
DungeonWorld *world = getDungeonWorld(); - FOR loop
uint8_t floor = 0U; floor < DUNGEON_FLOOR_COUNT; ++floorRepeat body while the loop condition permits; then continue below.
- FOR loop
uint8_t y = 0U; y < DUNGEON_HEIGHT; ++yRepeat body while the loop condition permits; then continue below.
- FOR loop
uint8_t x = 0U; x < DUNGEON_WIDTH; ++xRepeat body while the loop condition permits; then continue below.
- Execute
const DungeonCell *cell = &world->floors[floor].cells[y][x]; - Decision
(cell->eventFlags & DUNGEON_EVENT_KEY) != 0U && (cell->stateFlags & DUNGEON_STATE_KEY_COLLECTED) == 0UTrue- Execute
*position = (DungeonPosition){floor, x, y}; - Exit this function
return true;
FalseContinue after this decision.
- Exit this function
return false;
Read original function source
static bool aiFindUncollectedKey(DungeonPosition *position)
{
DungeonWorld *world = getDungeonWorld();
for (uint8_t floor = 0U; floor < DUNGEON_FLOOR_COUNT; ++floor)
for (uint8_t y = 0U; y < DUNGEON_HEIGHT; ++y)
for (uint8_t x = 0U; x < DUNGEON_WIDTH; ++x) {
const DungeonCell *cell = &world->floors[floor].cells[y][x];
if ((cell->eventFlags & DUNGEON_EVENT_KEY) != 0U &&
(cell->stateFlags & DUNGEON_STATE_KEY_COLLECTED) == 0U) {
*position = (DungeonPosition){floor, x, y};
return true;
}
}
return false;
}aiChooseMove() · source line 537
static bool aiChooseMove(ai_option_t *option)
Calls into this documentation:
- Execute
DungeonPlayerState *player = getDungeonPlayerState(); - Execute
DungeonWorld *world = getDungeonWorld(); - Execute
DungeonPosition target; - Decision
player->keys == 0U && (player->runFlags & DUNGEON_RUN_KEY_REVEALED) != 0U && aiFindUncollectedKey(&target)True- Exit this function
return aiFindPath(target, option);
FalseContinue after this decision.
- Decision
player->keys > 0U && player_position.floor < DUNGEON_FLOOR_COUNT - 1UTrue- FOR loop
uint8_t index = 0U; index < DUNGEON_FLOOR_COUNT - 1U; ++indexRepeat body while the loop condition permits; then continue below.
- Decision
world->stairLinks[index].upper.floor == player_position.floorTrue- Exit this function
return aiSamePosition(player_position, world->stairLinks[index].upper) ? ((*option = AI_DOWN), true) : aiFindPath(world->stairLinks[index].upper, option);
FalseContinue after this decision.
FalseContinue after this decision.
- Exit this function
return aiNearestUnexplored(option);
Read original function source
static bool aiChooseMove(ai_option_t *option)
{
DungeonPlayerState *player = getDungeonPlayerState();
DungeonWorld *world = getDungeonWorld();
DungeonPosition target;
if (player->keys == 0U && (player->runFlags & DUNGEON_RUN_KEY_REVEALED) != 0U &&
aiFindUncollectedKey(&target)) return aiFindPath(target, option);
if (player->keys > 0U && player_position.floor < DUNGEON_FLOOR_COUNT - 1U) {
for (uint8_t index = 0U; index < DUNGEON_FLOOR_COUNT - 1U; ++index)
if (world->stairLinks[index].upper.floor == player_position.floor)
return aiSamePosition(player_position, world->stairLinks[index].upper) ? ((*option = AI_DOWN), true) : aiFindPath(world->stairLinks[index].upper, option);
}
return aiNearestUnexplored(option);
}aiMoveOnce() · source line 552
static void aiMoveOnce(void)
Calls into this documentation:
- Execute
ai_option_t option; - Decision
!aiChooseMove(&option)True- Exit this function
return;
FalseContinue after this decision.
- Decision
ai_reverse_stair_blocked && ((option == AI_UP) == ai_blocked_stair_up)True- Exit this function
return;
FalseContinue after this decision.
- Decision
option == AI_UP || option == AI_DOWNTrue- Decision
moveStairs(option == AI_UP)True- Execute
ai_reverse_stair_blocked = true; - Execute
ai_blocked_stair_up = option == AI_DOWN; - Execute
ai_shop_visited = false;
FalseContinue after this decision.
- Exit this function
return;
FalseContinue after this decision.
- Execute
const uint8_t exits[] = { DUNGEON_EXIT_NORTH, DUNGEON_EXIT_EAST, DUNGEON_EXIT_SOUTH, DUNGEON_EXIT_WEST }; - Decision
movePlayer(exits[option])True- Execute
ai_reverse_stair_blocked = false; - Execute
ai_shop_visited = false;
FalseContinue after this decision.
Read original function source
static void aiMoveOnce(void)
{
ai_option_t option;
if (!aiChooseMove(&option)) return;
if (ai_reverse_stair_blocked && ((option == AI_UP) == ai_blocked_stair_up)) return;
if (option == AI_UP || option == AI_DOWN) {
if (moveStairs(option == AI_UP)) {
ai_reverse_stair_blocked = true;
ai_blocked_stair_up = option == AI_DOWN;
ai_shop_visited = false;
}
return;
}
const uint8_t exits[] = { DUNGEON_EXIT_NORTH, DUNGEON_EXIT_EAST, DUNGEON_EXIT_SOUTH, DUNGEON_EXIT_WEST };
if (movePlayer(exits[option])) {
ai_reverse_stair_blocked = false;
ai_shop_visited = false;
}
}aiChooseCombatAction() · source line 572
static void aiChooseCombatAction(void)
Calls into this documentation:
- Execute
const DungeonPlayerState *player = getDungeonPlayerState(); - Decision
player->health <= combat_state.health / 2UTrue- Execute
selected_combat_option = COMBAT_RUN;
False- Decision
player->healthPotions > 0U && player->health <= player->maxHealth / 2UTrue- Execute
selected_combat_option = COMBAT_POTION;
False- Execute
selected_combat_option = COMBAT_ATTACK;
Read original function source
static void aiChooseCombatAction(void)
{
const DungeonPlayerState *player = getDungeonPlayerState();
if (player->health <= combat_state.health / 2U) selected_combat_option = COMBAT_RUN;
else if (player->healthPotions > 0U && player->health <= player->maxHealth / 2U) selected_combat_option = COMBAT_POTION;
else selected_combat_option = COMBAT_ATTACK;
}startDungeon() · source line 583
void startDungeon(uint32_t now_ms)
Calls into this documentation:
- Execute
running = true; - Execute
play_timer_running = false; - Execute
last_play_time_ms = now_ms; - Execute
state = DUNGEON_START_MENU; - Execute
selected_option = 0; - Execute
image_visible = false; - Execute
decode_pending = false; - Execute
waiting_for_centre = true; - Execute
held_direction = 0; - Execute
repeating = false; - Execute
map_visible = false; - Execute
status_visible = false; - Execute
map_floor = 0; - Execute
map_page = 0; - Execute
status_page = 0; - Execute
reward_text[0] = '\0'; - Execute
combat_text[0] = '\0'; - Execute
shop_text[0] = '\0'; - Execute
creation_trace[0] = '\0'; - Execute
trap_damage = 0U; - Execute
combat_levels_gained = 0U; - Execute
reward_is_key = false; - Execute
combat_victory = false; - Execute
ai_mode = false; - Execute
ai_start_at = 0U; - Execute
ai_start_seconds = 2U; - Execute
ai_last_move = 0U; - Execute
ai_reverse_stair_blocked = false; - Execute
ai_shop_visited = false; - Execute
ai_last_shop_option = 4U; - Execute
ai_speed_index = AI_SPEED_DEFAULT_INDEX; - Execute
trap_defeated = false; - Execute
combat_result_title = NULL; - Execute
initDungeonConfig(); - Execute
initDungeonExplored(); - Execute
initDungeonPlayer(); - Execute
memset(&player_result, 0, sizeof(player_result)); - Execute
player_result.error = DUNGEON_PLAYER_OK; - Execute
memset(&explored_result, 0, sizeof(explored_result)); - Execute
explored_result.error = DUNGEON_EXPLORED_OK; - Execute
memset(&history_result, 0, sizeof(history_result)); - Execute
history_result.error = DUNGEON_HISTORY_OK; - Execute
completion_rank = 0U; - Decision
!loadDungeonWorld(&storage_result)True- Exit this function
return;
FalseContinue after this decision.
- Decision
!prepareEntranceImage()True- Execute
storage_result.error = DUNGEON_STORAGE_READ_FAILED; - Exit this function
return;
FalseContinue after this decision.
- Execute
prepareStartBackground();
Read original function source
void startDungeon(uint32_t now_ms)
{
running = true;
play_timer_running = false;
last_play_time_ms = now_ms;
state = DUNGEON_START_MENU;
selected_option = 0;
image_visible = false;
decode_pending = false;
waiting_for_centre = true;
held_direction = 0;
repeating = false;
map_visible = false;
status_visible = false;
map_floor = 0;
map_page = 0;
status_page = 0;
reward_text[0] = '\0';
combat_text[0] = '\0';
shop_text[0] = '\0';
creation_trace[0] = '\0';
trap_damage = 0U;
combat_levels_gained = 0U;
reward_is_key = false;
combat_victory = false;
ai_mode = false;
ai_start_at = 0U;
ai_start_seconds = 2U;
ai_last_move = 0U;
ai_reverse_stair_blocked = false;
ai_shop_visited = false;
ai_last_shop_option = 4U;
ai_speed_index = AI_SPEED_DEFAULT_INDEX;
trap_defeated = false;
combat_result_title = NULL;
initDungeonConfig();
initDungeonExplored();
initDungeonPlayer();
memset(&player_result, 0, sizeof(player_result));
player_result.error = DUNGEON_PLAYER_OK;
memset(&explored_result, 0, sizeof(explored_result));
explored_result.error = DUNGEON_EXPLORED_OK;
memset(&history_result, 0, sizeof(history_result));
history_result.error = DUNGEON_HISTORY_OK;
completion_rank = 0U;
if (!loadDungeonWorld(&storage_result)) return;
if (!prepareEntranceImage()) {
storage_result.error = DUNGEON_STORAGE_READ_FAILED;
return;
}
prepareStartBackground();
}stopDungeon() · source line 636
void stopDungeon(uint32_t now_ms)
Calls into this documentation:
- Execute
commitPlayTime(now_ms); - Decision
play_timer_runningTrue- Execute
(void)saveDungeonPlayer(&player_result);
FalseContinue after this decision.
- Execute
play_timer_running = false; - Execute
running = false; - Execute
decode_pending = false; - Execute
image_visible = false;
Read original function source
void stopDungeon(uint32_t now_ms)
{
commitPlayTime(now_ms);
if (play_timer_running) (void)saveDungeonPlayer(&player_result);
play_timer_running = false;
running = false;
decode_pending = false;
image_visible = false;
}combatOptionCount() · source line 646
static unsigned combatOptionCount(void)
Calls into this documentation:
- Exit this function
return getDungeonPlayerState()->healthPotions > 0U ? 3U : 2U;
Read original function source
static unsigned combatOptionCount(void)
{
return getDungeonPlayerState()->healthPotions > 0U ? 3U : 2U;
}combatEnemyName() · source line 651
static const char *combatEnemyName(uint8_t enemy_type)
- Select a case
enemy_typeEnter matching case; follow source fall-through until a break, return or end.
- Case / label
case DUNGEON_ENEMY_LEVEL_1: - Exit this function
return "LEVEL 1"; - Case / label
case DUNGEON_ENEMY_LEVEL_2: - Exit this function
return "LEVEL 2"; - Case / label
case DUNGEON_ENEMY_LEVEL_3: - Exit this function
return "LEVEL 3"; - Case / label
case DUNGEON_ENEMY_LEVEL_4: - Exit this function
return "LEVEL 4"; - Case / label
default: - Exit this function
return "DRAGON";
Read original function source
static const char *combatEnemyName(uint8_t enemy_type)
{
switch (enemy_type) {
case DUNGEON_ENEMY_LEVEL_1: return "LEVEL 1";
case DUNGEON_ENEMY_LEVEL_2: return "LEVEL 2";
case DUNGEON_ENEMY_LEVEL_3: return "LEVEL 3";
case DUNGEON_ENEMY_LEVEL_4: return "LEVEL 4";
default: return "DRAGON";
}
}returnToRoom() · source line 662
static void returnToRoom(void)
Calls into this documentation:
- Decision
!prepareRoomImage(&player_position)True- Execute
player_result.error = DUNGEON_PLAYER_INVALID_POSITION; - Execute
state = DUNGEON_NOTICE; - Exit this function
return;
FalseContinue after this decision.
- Execute
state = DUNGEON_ROOM; - Execute
image_visible = false; - Execute
decode_pending = true;
Read original function source
static void returnToRoom(void)
{
if (!prepareRoomImage(&player_position)) {
player_result.error = DUNGEON_PLAYER_INVALID_POSITION;
state = DUNGEON_NOTICE;
return;
}
state = DUNGEON_ROOM;
image_visible = false;
decode_pending = true;
}beginDungeonCreation() · source line 674
static void beginDungeonCreation(bool delete_old_files, uint32_t now_ms)
- Execute
memset(&creation_generation_stats, 0, sizeof(creation_generation_stats)); - Execute
creation_step = delete_old_files ? CREATE_DELETE : CREATE_GENERATE; - Execute
creation_step_at = now_ms; - Execute
state = DUNGEON_CREATING; - Execute
image_visible = false; - Execute
decode_pending = false;
Read original function source
static void beginDungeonCreation(bool delete_old_files, uint32_t now_ms)
{
memset(&creation_generation_stats, 0, sizeof(creation_generation_stats));
creation_step = delete_old_files ? CREATE_DELETE : CREATE_GENERATE;
creation_step_at = now_ms;
state = DUNGEON_CREATING;
image_visible = false;
decode_pending = false;
}advanceDungeonCreation() · source line 684
static void advanceDungeonCreation(uint32_t now_ms)
Calls into this documentation:
- Decision
now_ms - creation_step_at < DUNGEON_CREATION_STAGE_PRESENT_MSTrue- Exit this function
return;
FalseContinue after this decision.
- Decision
creation_step == CREATE_DELETETrue- Decision
!deleteDungeonDataFiles(&delete_result)True- Execute
state = DUNGEON_NOTICE; - Exit this function
return;
FalseContinue after this decision.
- Execute
prepareNewDungeonWorldStorage(); - Execute
creation_step = CREATE_GENERATE; - Execute
creation_step_at = now_ms; - Exit this function
return;
FalseContinue after this decision.
- Decision
creation_step == CREATE_GENERATETrue- Decision
!generateDungeonWorld(&creation_generation_stats)True- Execute
state = DUNGEON_NOTICE; - Exit this function
return;
FalseContinue after this decision.
- Execute
creation_step = CREATE_WORLD; - Execute
creation_step_at = now_ms; - Exit this function
return;
FalseContinue after this decision.
- Decision
creation_step == CREATE_WORLDTrue- Decision
!saveDungeonWorld(&storage_result)True- Execute
state = DUNGEON_NOTICE; - Exit this function
return;
FalseContinue after this decision.
- Execute
creation_step = CREATE_PLAYER; - Execute
creation_step_at = now_ms; - Exit this function
return;
FalseContinue after this decision.
- Decision
creation_step == CREATE_PLAYERTrue- Decision
!resetDungeonPlayer(&player_result)True- Execute
state = DUNGEON_NOTICE; - Exit this function
return;
FalseContinue after this decision.
- Execute
creation_step = CREATE_EXPLORED; - Execute
creation_step_at = now_ms; - Exit this function
return;
FalseContinue after this decision.
- Decision
creation_step == CREATE_EXPLOREDTrue- Decision
!resetDungeonExplored(&getDungeonPlayerState()->position, &explored_result)True- Execute
state = DUNGEON_NOTICE; - Exit this function
return;
FalseContinue after this decision.
- Execute
player_position = getDungeonPlayerState()->position; - Execute
creation_step = CREATE_READY; - Execute
creation_ready_at = now_ms; - Execute
creation_step_at = now_ms; - Exit this function
return;
FalseContinue after this decision.
- Decision
now_ms - creation_ready_at < DUNGEON_CREATION_READY_MSTrue- Exit this function
return;
FalseContinue after this decision.
- Decision
!prepareRoomImage(&player_position)True- Execute
player_result.error = DUNGEON_PLAYER_INVALID_POSITION; - Execute
state = DUNGEON_NOTICE; - Exit this function
return;
FalseContinue after this decision.
- Execute
state = DUNGEON_ROOM; - Execute
startPlayTimer(now_ms); - Execute
image_visible = false; - Execute
decode_pending = true;
Read original function source
static void advanceDungeonCreation(uint32_t now_ms)
{
if (now_ms - creation_step_at < DUNGEON_CREATION_STAGE_PRESENT_MS) return;
if (creation_step == CREATE_DELETE) {
if (!deleteDungeonDataFiles(&delete_result)) { state = DUNGEON_NOTICE; return; }
prepareNewDungeonWorldStorage();
creation_step = CREATE_GENERATE;
creation_step_at = now_ms;
return;
}
if (creation_step == CREATE_GENERATE) {
if (!generateDungeonWorld(&creation_generation_stats)) { state = DUNGEON_NOTICE; return; }
creation_step = CREATE_WORLD;
creation_step_at = now_ms;
return;
}
if (creation_step == CREATE_WORLD) {
if (!saveDungeonWorld(&storage_result)) { state = DUNGEON_NOTICE; return; }
creation_step = CREATE_PLAYER;
creation_step_at = now_ms;
return;
}
if (creation_step == CREATE_PLAYER) {
if (!resetDungeonPlayer(&player_result)) { state = DUNGEON_NOTICE; return; }
creation_step = CREATE_EXPLORED;
creation_step_at = now_ms;
return;
}
if (creation_step == CREATE_EXPLORED) {
if (!resetDungeonExplored(&getDungeonPlayerState()->position, &explored_result)) { state = DUNGEON_NOTICE; return; }
player_position = getDungeonPlayerState()->position;
creation_step = CREATE_READY;
creation_ready_at = now_ms;
creation_step_at = now_ms;
return;
}
if (now_ms - creation_ready_at < DUNGEON_CREATION_READY_MS) return;
if (!prepareRoomImage(&player_position)) { player_result.error = DUNGEON_PLAYER_INVALID_POSITION; state = DUNGEON_NOTICE; return; }
state = DUNGEON_ROOM;
startPlayTimer(now_ms);
image_visible = false;
decode_pending = true;
}completeCombatVictory() · source line 728
static void completeCombatVictory(void)
Calls into this documentation:
- Execute
DungeonCell *cell = &getDungeonWorld()->floors[player_position.floor].cells[player_position.y][player_position.x]; - Execute
DungeonPlayerState *player = getDungeonPlayerState(); - Execute
const DungeonPlayerState previous_player = *player; - Execute
const uint16_t previous_flags = cell->stateFlags; - Execute
const DungeonPosition *exit = &getDungeonWorld()->exit; - Execute
const bool final_dragon = combat_state.enemyType == DUNGEON_ENEMY_DRAGON && player_position.floor == exit->floor && player_position.x == exit->x && player_position.y == exit->y; - Execute
combat_levels_gained = addDungeonPlayerExperience(combat_state.xpReward); - Execute
cell->stateFlags |= DUNGEON_STATE_COMPLETED; - Decision
final_dragonTrue- Execute
cell->stateFlags |= DUNGEON_STATE_MESSAGE_READ | DUNGEON_STATE_UNLOCKED; - Execute
player->runFlags |= DUNGEON_RUN_COMPLETED;
FalseContinue after this decision.
- Decision
!saveDungeonWorld(&storage_result) || !saveDungeonPlayer(&player_result)True- Execute
*player = previous_player; - Execute
cell->stateFlags = previous_flags; - Execute
saveDungeonWorld(&storage_result); - Execute
state = DUNGEON_NOTICE; - Exit this function
return;
FalseContinue after this decision.
- Decision
final_dragonTrue- Execute
recordDungeonCompletion(player, &history_result, &completion_rank); - Execute
state = DUNGEON_COMPLETE; - Exit this function
return;
FalseContinue after this decision.
- Decision
combat_levels_gained > 0UTrue- Execute
snprintf(combat_text, sizeof(combat_text), "+%lu XP LEVEL +%u", (unsigned long)combat_state.xpReward, (unsigned)combat_levels_gained);
False- Execute
snprintf(combat_text, sizeof(combat_text), "+%lu XP", (unsigned long)combat_state.xpReward);
- Execute
combat_victory = true; - Execute
combat_result_title = "VICTORY"; - Execute
combat_result_color = COLOR_GREEN; - Execute
state = DUNGEON_COMBAT_RESULT;
Read original function source
static void completeCombatVictory(void)
{
DungeonCell *cell = &getDungeonWorld()->floors[player_position.floor].cells[player_position.y][player_position.x];
DungeonPlayerState *player = getDungeonPlayerState();
const DungeonPlayerState previous_player = *player;
const uint16_t previous_flags = cell->stateFlags;
const DungeonPosition *exit = &getDungeonWorld()->exit;
const bool final_dragon = combat_state.enemyType == DUNGEON_ENEMY_DRAGON && player_position.floor == exit->floor && player_position.x == exit->x && player_position.y == exit->y;
combat_levels_gained = addDungeonPlayerExperience(combat_state.xpReward);
cell->stateFlags |= DUNGEON_STATE_COMPLETED;
if (final_dragon) {
cell->stateFlags |= DUNGEON_STATE_MESSAGE_READ | DUNGEON_STATE_UNLOCKED;
player->runFlags |= DUNGEON_RUN_COMPLETED;
}
if (!saveDungeonWorld(&storage_result) || !saveDungeonPlayer(&player_result)) {
*player = previous_player;
cell->stateFlags = previous_flags;
saveDungeonWorld(&storage_result);
state = DUNGEON_NOTICE;
return;
}
if (final_dragon) {
recordDungeonCompletion(player, &history_result, &completion_rank);
state = DUNGEON_COMPLETE;
return;
}
if (combat_levels_gained > 0U) snprintf(combat_text, sizeof(combat_text), "+%lu XP LEVEL +%u", (unsigned long)combat_state.xpReward, (unsigned)combat_levels_gained);
else snprintf(combat_text, sizeof(combat_text), "+%lu XP", (unsigned long)combat_state.xpReward);
combat_victory = true;
combat_result_title = "VICTORY";
combat_result_color = COLOR_GREEN;
state = DUNGEON_COMBAT_RESULT;
}respawnAfterCombatDefeat() · source line 762
static void respawnAfterCombatDefeat(void)
Calls into this documentation:
- Execute
DungeonPlayerState *player = getDungeonPlayerState(); - Execute
const DungeonPlayerState previous_player = *player; - Execute
DungeonPosition respawn = getDungeonWorld()->entrance; - Decision
player_position.floor > 0UTrue- Execute
respawn = getDungeonWorld()->stairLinks[player_position.floor - 1U].lower;
FalseContinue after this decision.
- Execute
player->position = respawn; - Execute
player->health = (uint16_t)(player->maxHealth / 2U + player->maxHealth % 2U); - Decision
!saveDungeonPlayer(&player_result)True- Execute
*player = previous_player; - Execute
state = DUNGEON_NOTICE; - Exit this function
return;
FalseContinue after this decision.
- Execute
player_position = respawn; - Execute
recordExplored(&player_position); - Execute
snprintf(combat_text, sizeof(combat_text), "RETURNED WITH HALF HEALTH"); - Execute
combat_victory = false; - Execute
combat_result_title = "DEFEATED"; - Execute
combat_result_color = COLOR_RED; - Execute
state = DUNGEON_COMBAT_RESULT;
Read original function source
static void respawnAfterCombatDefeat(void)
{
DungeonPlayerState *player = getDungeonPlayerState();
const DungeonPlayerState previous_player = *player;
DungeonPosition respawn = getDungeonWorld()->entrance;
if (player_position.floor > 0U) respawn = getDungeonWorld()->stairLinks[player_position.floor - 1U].lower;
player->position = respawn;
player->health = (uint16_t)(player->maxHealth / 2U + player->maxHealth % 2U);
if (!saveDungeonPlayer(&player_result)) {
*player = previous_player;
state = DUNGEON_NOTICE;
return;
}
player_position = respawn;
recordExplored(&player_position);
snprintf(combat_text, sizeof(combat_text), "RETURNED WITH HALF HEALTH");
combat_victory = false;
combat_result_title = "DEFEATED";
combat_result_color = COLOR_RED;
state = DUNGEON_COMBAT_RESULT;
}resolveCombatAction() · source line 784
static void resolveCombatAction(void)
Calls into this documentation:
- Execute
DungeonPlayerState *player = getDungeonPlayerState(); - Execute
DungeonCombatResult result; - Decision
selected_combat_option == COMBAT_RUNTrue- Execute
result = dungeonCombatAttemptRun(&combat_state, player); - Decision
result == DUNGEON_COMBAT_ESCAPEDTrue- Execute
player->position = encounter_source; - Decision
!saveDungeonPlayer(&player_result)True- Execute
state = DUNGEON_NOTICE; - Exit this function
return;
FalseContinue after this decision.
- Execute
player_position = encounter_source; - Execute
snprintf(combat_text, sizeof(combat_text), "ESCAPED"); - Execute
combat_victory = false; - Execute
combat_result_title = "ESCAPED"; - Execute
combat_result_color = COLOR_CYAN; - Execute
state = DUNGEON_COMBAT_RESULT; - Exit this function
return;
FalseContinue after this decision.
- Execute
snprintf(combat_text, sizeof(combat_text), "RUN FAILED ENEMY %lu", (unsigned long)combat_state.lastEnemyDamage);
False- Decision
selected_combat_option == COMBAT_POTIONTrue- Execute
result = dungeonCombatUsePotion(&combat_state, player); - Execute
snprintf(combat_text, sizeof(combat_text), "POTION ENEMY %lu", (unsigned long)combat_state.lastEnemyDamage);
False- Execute
result = dungeonCombatAttack(&combat_state, player); - Execute
snprintf(combat_text, sizeof(combat_text), "YOU %lu ENEMY %lu", (unsigned long)combat_state.lastPlayerDamage, (unsigned long)combat_state.lastEnemyDamage);
- Decision
result == DUNGEON_COMBAT_ENEMY_DEFEATEDTrue- Execute
completeCombatVictory(); - Exit this function
return;
FalseContinue after this decision.
- Decision
result == DUNGEON_COMBAT_PLAYER_DEFEATEDTrue- Execute
respawnAfterCombatDefeat(); - Exit this function
return;
FalseContinue after this decision.
- Decision
selected_combat_option >= combatOptionCount()True- Execute
selected_combat_option = COMBAT_ATTACK;
FalseContinue after this decision.
- Decision
!saveDungeonPlayer(&player_result)True- Execute
state = DUNGEON_NOTICE;
FalseContinue after this decision.
Read original function source
static void resolveCombatAction(void)
{
DungeonPlayerState *player = getDungeonPlayerState();
DungeonCombatResult result;
if (selected_combat_option == COMBAT_RUN) {
result = dungeonCombatAttemptRun(&combat_state, player);
if (result == DUNGEON_COMBAT_ESCAPED) {
player->position = encounter_source;
if (!saveDungeonPlayer(&player_result)) { state = DUNGEON_NOTICE; return; }
player_position = encounter_source;
snprintf(combat_text, sizeof(combat_text), "ESCAPED");
combat_victory = false;
combat_result_title = "ESCAPED";
combat_result_color = COLOR_CYAN;
state = DUNGEON_COMBAT_RESULT;
return;
}
snprintf(combat_text, sizeof(combat_text), "RUN FAILED ENEMY %lu", (unsigned long)combat_state.lastEnemyDamage);
} else if (selected_combat_option == COMBAT_POTION) {
result = dungeonCombatUsePotion(&combat_state, player);
snprintf(combat_text, sizeof(combat_text), "POTION ENEMY %lu", (unsigned long)combat_state.lastEnemyDamage);
} else {
result = dungeonCombatAttack(&combat_state, player);
snprintf(combat_text, sizeof(combat_text), "YOU %lu ENEMY %lu", (unsigned long)combat_state.lastPlayerDamage, (unsigned long)combat_state.lastEnemyDamage);
}
if (result == DUNGEON_COMBAT_ENEMY_DEFEATED) { completeCombatVictory(); return; }
if (result == DUNGEON_COMBAT_PLAYER_DEFEATED) { respawnAfterCombatDefeat(); return; }
if (selected_combat_option >= combatOptionCount()) selected_combat_option = COMBAT_ATTACK;
if (!saveDungeonPlayer(&player_result)) state = DUNGEON_NOTICE;
}shopUpgradeCost() · source line 815
static uint32_t shopUpgradeCost(uint32_t base_cost)
Calls into this documentation:
- Execute
const DungeonConfig *config = getDungeonConfig(); - Execute
uint32_t cost = base_cost; - FOR loop
uint16_t purchase = 0U; purchase < getDungeonPlayerState()->shopPurchaseCount; ++purchaseRepeat body while the loop condition permits; then continue below.
- Execute
const uint64_t increased = ((uint64_t)cost * (100U + config->shopCostIncreasePercent) + 99U) / 100U; - Decision
increased >= UINT32_MAXTrue- Exit this function
return UINT32_MAX;
FalseContinue after this decision.
- Execute
cost = (uint32_t)increased;
- Exit this function
return cost;
Read original function source
static uint32_t shopUpgradeCost(uint32_t base_cost)
{
const DungeonConfig *config = getDungeonConfig();
uint32_t cost = base_cost;
for (uint16_t purchase = 0U; purchase < getDungeonPlayerState()->shopPurchaseCount; ++purchase) {
const uint64_t increased = ((uint64_t)cost * (100U + config->shopCostIncreasePercent) + 99U) / 100U;
if (increased >= UINT32_MAX) return UINT32_MAX;
cost = (uint32_t)increased;
}
return cost;
}shopChoiceCost() · source line 827
static uint32_t shopChoiceCost(unsigned choice)
Calls into this documentation:
- Execute
const DungeonConfig *config = getDungeonConfig(); - Decision
choice == 0UTrue- Exit this function
return config->shopRestoreCost;
FalseContinue after this decision.
- Decision
choice == 1UTrue- Exit this function
return shopUpgradeCost(config->shopHealthBaseCost);
FalseContinue after this decision.
- Decision
choice == 2UTrue- Exit this function
return shopUpgradeCost(config->shopAttackBaseCost);
FalseContinue after this decision.
- Exit this function
return choice == 3U ? shopUpgradeCost(config->shopDefenceBaseCost) : 0U;
Read original function source
static uint32_t shopChoiceCost(unsigned choice)
{
const DungeonConfig *config = getDungeonConfig();
if (choice == 0U) return config->shopRestoreCost;
if (choice == 1U) return shopUpgradeCost(config->shopHealthBaseCost);
if (choice == 2U) return shopUpgradeCost(config->shopAttackBaseCost);
return choice == 3U ? shopUpgradeCost(config->shopDefenceBaseCost) : 0U;
}applyShopChoice() · source line 836
static void applyShopChoice(void)
Calls into this documentation:
- Execute
DungeonPlayerState *player = getDungeonPlayerState(); - Execute
const DungeonPlayerState previous_player = *player; - Execute
const DungeonConfig *config = getDungeonConfig(); - Decision
selected_shop_option == 4UTrue- Execute
returnToRoom(); - Exit this function
return;
FalseContinue after this decision.
- Execute
const uint32_t cost = shopChoiceCost(selected_shop_option); - Decision
player->gold < costTrue- Execute
snprintf(shop_text, sizeof(shop_text), "NOT ENOUGH GOLD"); - Exit this function
return;
FalseContinue after this decision.
- Decision
selected_shop_option == 0UTrue- Decision
player->health >= player->maxHealthTrue- Execute
snprintf(shop_text, sizeof(shop_text), "HEALTH ALREADY FULL"); - Exit this function
return;
FalseContinue after this decision.
- Execute
player->health = player->maxHealth; - Execute
snprintf(shop_text, sizeof(shop_text), "HEALTH RESTORED");
False- Decision
selected_shop_option == 1UTrue- Decision
config->shopHealthGain == 0U || config->shopHealthGain > UINT16_MAX - player->maxHealthTrue- Execute
snprintf(shop_text, sizeof(shop_text), "MAX HP LIMIT"); - Exit this function
return;
FalseContinue after this decision.
- Execute
player->maxHealth += config->shopHealthGain; - Execute
player->health = player->health <= UINT16_MAX - config->shopHealthGain ? player->health + config->shopHealthGain : UINT16_MAX; - Execute
snprintf(shop_text, sizeof(shop_text), "MAX HP INCREASED");
False- Decision
selected_shop_option == 2UTrue- Decision
config->shopAttackGain == 0U || config->shopAttackGain > UINT16_MAX - player->attackTrue- Execute
snprintf(shop_text, sizeof(shop_text), "ATTACK LIMIT"); - Exit this function
return;
FalseContinue after this decision.
- Execute
player->attack += config->shopAttackGain; - Execute
snprintf(shop_text, sizeof(shop_text), "ATTACK INCREASED");
False- Decision
config->shopDefenceGain == 0U || config->shopDefenceGain > UINT16_MAX - player->defenceTrue- Execute
snprintf(shop_text, sizeof(shop_text), "DEFENCE LIMIT"); - Exit this function
return;
FalseContinue after this decision.
- Execute
player->defence += config->shopDefenceGain; - Execute
snprintf(shop_text, sizeof(shop_text), "DEFENCE INCREASED");
- Execute
player->gold -= cost; - Decision
player->shopPurchaseCount < UINT16_MAXTrue- Execute
++player->shopPurchaseCount;
FalseContinue after this decision.
- Decision
!saveDungeonPlayer(&player_result)True- Execute
*player = previous_player; - Execute
state = DUNGEON_NOTICE;
FalseContinue after this decision.
Read original function source
static void applyShopChoice(void)
{
DungeonPlayerState *player = getDungeonPlayerState();
const DungeonPlayerState previous_player = *player;
const DungeonConfig *config = getDungeonConfig();
if (selected_shop_option == 4U) { returnToRoom(); return; }
const uint32_t cost = shopChoiceCost(selected_shop_option);
if (player->gold < cost) { snprintf(shop_text, sizeof(shop_text), "NOT ENOUGH GOLD"); return; }
if (selected_shop_option == 0U) {
if (player->health >= player->maxHealth) { snprintf(shop_text, sizeof(shop_text), "HEALTH ALREADY FULL"); return; }
player->health = player->maxHealth;
snprintf(shop_text, sizeof(shop_text), "HEALTH RESTORED");
} else if (selected_shop_option == 1U) {
if (config->shopHealthGain == 0U || config->shopHealthGain > UINT16_MAX - player->maxHealth) { snprintf(shop_text, sizeof(shop_text), "MAX HP LIMIT"); return; }
player->maxHealth += config->shopHealthGain;
player->health = player->health <= UINT16_MAX - config->shopHealthGain ? player->health + config->shopHealthGain : UINT16_MAX;
snprintf(shop_text, sizeof(shop_text), "MAX HP INCREASED");
} else if (selected_shop_option == 2U) {
if (config->shopAttackGain == 0U || config->shopAttackGain > UINT16_MAX - player->attack) { snprintf(shop_text, sizeof(shop_text), "ATTACK LIMIT"); return; }
player->attack += config->shopAttackGain;
snprintf(shop_text, sizeof(shop_text), "ATTACK INCREASED");
} else {
if (config->shopDefenceGain == 0U || config->shopDefenceGain > UINT16_MAX - player->defence) { snprintf(shop_text, sizeof(shop_text), "DEFENCE LIMIT"); return; }
player->defence += config->shopDefenceGain;
snprintf(shop_text, sizeof(shop_text), "DEFENCE INCREASED");
}
player->gold -= cost;
if (player->shopPurchaseCount < UINT16_MAX) ++player->shopPurchaseCount;
if (!saveDungeonPlayer(&player_result)) {
*player = previous_player;
state = DUNGEON_NOTICE;
}
}aiChooseShopOption() · source line 870
static unsigned aiChooseShopOption(void)
Calls into this documentation:
- Execute
const DungeonPlayerState *player = getDungeonPlayerState(); - Execute
const DungeonConfig *config = getDungeonConfig(); - Decision
(uint32_t)player->health * 2U < player->maxHealth && player->health < player->maxHealth && player->gold >= shopChoiceCost(0U)True- Exit this function
return 0U;
FalseContinue after this decision.
- Execute
const unsigned options[] = {1U, 2U, 3U}; - Execute
const uint8_t weights[] = {2U, 1U, 4U}; - Execute
uint8_t total = 0U; - FOR loop
unsigned index = 0U; index < 3U; ++indexRepeat body while the loop condition permits; then continue below.
- Execute
const unsigned option = options[index]; - Execute
const bool valid = option == 1U ? config->shopHealthGain > 0U && config->shopHealthGain <= UINT16_MAX - player->maxHealth : option == 2U ? config->shopAttackGain > 0U && config->shopAttackGain <= UINT16_MAX - player->attack : config->shopDefenceGain > 0U && config->shopDefenceGain <= UINT16_MAX - player->defence; - Decision
valid && player->gold >= shopChoiceCost(option) && option != ai_last_shop_optionTrue- Execute
total += weights[index];
FalseContinue after this decision.
- Decision
total == 0UTrue- Exit this function
return 4U;
FalseContinue after this decision.
- Execute
uint8_t ticket = (uint8_t)(esp_random() % total); - FOR loop
unsigned index = 0U; index < 3U; ++indexRepeat body while the loop condition permits; then continue below.
- Execute
const unsigned option = options[index]; - Execute
const bool valid = option == 1U ? config->shopHealthGain > 0U && config->shopHealthGain <= UINT16_MAX - player->maxHealth : option == 2U ? config->shopAttackGain > 0U && config->shopAttackGain <= UINT16_MAX - player->attack : config->shopDefenceGain > 0U && config->shopDefenceGain <= UINT16_MAX - player->defence; - Decision
!valid || player->gold < shopChoiceCost(option) || option == ai_last_shop_optionTrue- Next loop iteration
continue;
FalseContinue after this decision.
- Decision
ticket < weights[index]True- Exit this function
return option;
FalseContinue after this decision.
- Execute
ticket = (uint8_t)(ticket - weights[index]);
- Exit this function
return 4U;
Read original function source
static unsigned aiChooseShopOption(void)
{
const DungeonPlayerState *player = getDungeonPlayerState();
const DungeonConfig *config = getDungeonConfig();
if ((uint32_t)player->health * 2U < player->maxHealth &&
player->health < player->maxHealth && player->gold >= shopChoiceCost(0U)) return 0U;
const unsigned options[] = {1U, 2U, 3U};
const uint8_t weights[] = {2U, 1U, 4U};
uint8_t total = 0U;
for (unsigned index = 0U; index < 3U; ++index) {
const unsigned option = options[index];
const bool valid = option == 1U ? config->shopHealthGain > 0U && config->shopHealthGain <= UINT16_MAX - player->maxHealth :
option == 2U ? config->shopAttackGain > 0U && config->shopAttackGain <= UINT16_MAX - player->attack :
config->shopDefenceGain > 0U && config->shopDefenceGain <= UINT16_MAX - player->defence;
if (valid && player->gold >= shopChoiceCost(option) && option != ai_last_shop_option) total += weights[index];
}
if (total == 0U) return 4U;
uint8_t ticket = (uint8_t)(esp_random() % total);
for (unsigned index = 0U; index < 3U; ++index) {
const unsigned option = options[index];
const bool valid = option == 1U ? config->shopHealthGain > 0U && config->shopHealthGain <= UINT16_MAX - player->maxHealth :
option == 2U ? config->shopAttackGain > 0U && config->shopAttackGain <= UINT16_MAX - player->attack :
config->shopDefenceGain > 0U && config->shopDefenceGain <= UINT16_MAX - player->defence;
if (!valid || player->gold < shopChoiceCost(option) || option == ai_last_shop_option) continue;
if (ticket < weights[index]) return option;
ticket = (uint8_t)(ticket - weights[index]);
}
return 4U;
}dungeonAction() · source line 903
void dungeonAction(bool action, bool map_action, bool status_action, bool context_action, uint32_t now_ms)
Calls into this documentation:
- Decision
!runningTrue- Exit this function
return;
FalseContinue after this decision.
- Decision
state == DUNGEON_COMPLETE && actionTrue- Execute
menu_back(now_ms); - Exit this function
return;
FalseContinue after this decision.
- Decision
state == DUNGEON_HISTORY && actionTrue- Execute
state = DUNGEON_START_MENU; - Exit this function
return;
FalseContinue after this decision.
- Decision
state == DUNGEON_SHOPTrue- Decision
status_actionTrue- Execute
returnToRoom(); - Exit this function
return;
FalseContinue after this decision.
- Decision
actionTrue- Execute
applyShopChoice();
FalseContinue after this decision.
- Exit this function
return;
FalseContinue after this decision.
- Decision
state == DUNGEON_LEVEL_BONUSTrue- Decision
status_actionTrue- Execute
returnToRoom(); - Exit this function
return;
FalseContinue after this decision.
- Decision
actionTrue- Execute
DungeonPlayerState *player = getDungeonPlayerState(); - Execute
const DungeonPlayerState previous_player = *player; - Decision
applyDungeonPlayerStatChoice((uint8_t)selected_stat_option) && !saveDungeonPlayer(&player_result)True- Execute
*player = previous_player; - Execute
state = DUNGEON_NOTICE;
False- Decision
player->unspentStatPoints == 0UTrue- Execute
returnToRoom();
FalseContinue after this decision.
FalseContinue after this decision.
- Exit this function
return;
FalseContinue after this decision.
- Decision
state == DUNGEON_LOCKED_DOOR && actionTrue- Execute
DungeonPlayerState *player = getDungeonPlayerState(); - Execute
player->position = encounter_source; - Decision
!saveDungeonPlayer(&player_result)True- Execute
state = DUNGEON_NOTICE; - Exit this function
return;
FalseContinue after this decision.
- Execute
player_position = encounter_source; - Execute
returnToRoom(); - Exit this function
return;
FalseContinue after this decision.
- Decision
state == DUNGEON_TRAP_RESULT && actionTrue- Decision
!trap_defeated && startRoomEvent(&player_position)True- Exit this function
return;
FalseContinue after this decision.
- Execute
returnToRoom(); - Exit this function
return;
FalseContinue after this decision.
- Decision
state == DUNGEON_COMBAT_RESULT && actionTrue- Decision
combat_victory && startRoomEvent(&player_position)True- Exit this function
return;
FalseContinue after this decision.
- Execute
returnToRoom(); - Exit this function
return;
FalseContinue after this decision.
- Decision
state == DUNGEON_COMBAT && actionTrue- Execute
resolveCombatAction(); - Exit this function
return;
FalseContinue after this decision.
- Decision
state == DUNGEON_REWARD && actionTrue- Execute
returnToRoom(); - Exit this function
return;
FalseContinue after this decision.
- Decision
state == DUNGEON_ROOM && map_actionTrue- Decision
map_visibleTrue- Execute
map_visible = false; - Execute
waiting_for_centre = true; - Execute
returnToRoom(); - Exit this function
return;
FalseContinue after this decision.
FalseContinue after this decision.
- Decision
state == DUNGEON_ROOM && status_visible && context_action && status_page == 0U && getDungeonPlayerState()->unspentStatPoints > 0UTrue- Execute
status_visible = false; - Execute
selected_stat_option = 0U; - Execute
state = DUNGEON_LEVEL_BONUS; - Exit this function
return;
FalseContinue after this decision.
- Decision
state == DUNGEON_ROOM && !map_visible && !status_visible && context_action && isShopRoom(&getDungeonWorld()->floors[player_position.floor].cells[player_position.y][player_position.x])True- Execute
selected_shop_option = 0U; - Execute
shop_text[0] = '\0'; - Execute
state = DUNGEON_SHOP; - Execute
image_visible = false; - Execute
decode_pending = false; - Exit this function
return;
FalseContinue after this decision.
- Decision
state == DUNGEON_ROOM && status_actionTrue- Execute
map_visible = false; - Execute
status_visible = !status_visible; - Decision
!status_visibleTrue- Execute
image_visible = false; - Execute
decode_pending = true;
FalseContinue after this decision.
- Execute
waiting_for_centre = true; - Exit this function
return;
FalseContinue after this decision.
- Decision
state == DUNGEON_ROOM && status_visibleTrue- Decision
actionTrue- Execute
status_page = (status_page + 1U) % 3U;
FalseContinue after this decision.
- Exit this function
return;
FalseContinue after this decision.
- Decision
state == DUNGEON_ROOM && map_visibleTrue- Decision
actionTrue- Decision
map_page == 0UTrue- Execute
map_page = 1U;
False- Decision
map_page == 1UTrue- Execute
map_page = 3U;
False- Decision
map_page == 3UTrue- Execute
map_page = 2U;
False- Execute
map_page = 0U; - Execute
map_floor = (map_floor + 1U) % DUNGEON_FLOOR_COUNT;
FalseContinue after this decision.
- Execute
waiting_for_centre = true; - Exit this function
return;
FalseContinue after this decision.
- Decision
state == DUNGEON_ROOM && map_actionTrue- Execute
map_floor = player_position.floor; - Execute
map_page = (player_position.y / 20U) * 2U + (player_position.x / 20U); - Execute
map_visible = true; - Execute
waiting_for_centre = true; - Exit this function
return;
FalseContinue after this decision.
- Decision
!actionTrue- Exit this function
return;
FalseContinue after this decision.
- Decision
state == DUNGEON_START_MENU && storage_result.error != DUNGEON_STORAGE_OKTrue- Execute
traceDungeonCreation("A RECEIVED", 0, 0); - Execute
beginDungeonCreation(true, now_ms); - Exit this function
return;
FalseContinue after this decision.
- Decision
state == DUNGEON_START_MENU && storage_result.error == DUNGEON_STORAGE_OKTrue- Decision
selected_option == 0UTrue- Decision
!prepareDungeonPlayer(&player_result)True- Execute
state = DUNGEON_NOTICE; - Exit this function
return;
FalseContinue after this decision.
- Execute
state = DUNGEON_ROOM; - Execute
player_position = getDungeonPlayerState()->position; - Decision
!prepareRoomImage(&player_position)True- Execute
state = DUNGEON_NOTICE; - Execute
player_result.error = DUNGEON_PLAYER_INVALID_POSITION; - Exit this function
return;
FalseContinue after this decision.
- Decision
!prepareDungeonExplored(&player_position, &explored_result)True- Execute
state = DUNGEON_NOTICE; - Exit this function
return;
FalseContinue after this decision.
- Execute
startPlayTimer(now_ms); - Decision
startRoomEvent(&player_position)True- Exit this function
return;
FalseContinue after this decision.
- Execute
image_visible = false; - Execute
decode_pending = true;
False- Decision
selected_option == 1UTrue- Execute
beginDungeonCreation(false, now_ms); - Exit this function
return;
False- Decision
selected_option == 2UTrue- Execute
ai_mode = true; - Execute
ai_start_at = now_ms; - Execute
ai_start_seconds = 2U; - Execute
state = DUNGEON_AI_START;
False- Decision
selected_option == 3UTrue- Decision
!loadDungeonHistory(&history_result) && history_result.error == DUNGEON_HISTORY_OPEN_READ_FAILEDTrue- Execute
initDungeonHistory();
FalseContinue after this decision.
- Execute
state = DUNGEON_HISTORY;
False- Decision
selected_option == 4UTrue- Execute
menu_back(now_ms);
False- Execute
state = DUNGEON_NOTICE;
False- Decision
state == DUNGEON_NOTICETrue- Execute
state = DUNGEON_START_MENU;
FalseContinue after this decision.
Read original function source
void dungeonAction(bool action, bool map_action, bool status_action, bool context_action, uint32_t now_ms)
{
if (!running) return;
if (state == DUNGEON_COMPLETE && action) { menu_back(now_ms); return; }
if (state == DUNGEON_HISTORY && action) { state = DUNGEON_START_MENU; return; }
if (state == DUNGEON_SHOP) {
if (status_action) { returnToRoom(); return; }
if (action) applyShopChoice();
return;
}
if (state == DUNGEON_LEVEL_BONUS) {
if (status_action) { returnToRoom(); return; }
if (action) {
DungeonPlayerState *player = getDungeonPlayerState();
const DungeonPlayerState previous_player = *player;
if (applyDungeonPlayerStatChoice((uint8_t)selected_stat_option) && !saveDungeonPlayer(&player_result)) {
*player = previous_player;
state = DUNGEON_NOTICE;
} else if (player->unspentStatPoints == 0U) returnToRoom();
}
return;
}
if (state == DUNGEON_LOCKED_DOOR && action) {
DungeonPlayerState *player = getDungeonPlayerState();
player->position = encounter_source;
if (!saveDungeonPlayer(&player_result)) { state = DUNGEON_NOTICE; return; }
player_position = encounter_source;
returnToRoom();
return;
}
if (state == DUNGEON_TRAP_RESULT && action) {
if (!trap_defeated && startRoomEvent(&player_position)) return;
returnToRoom();
return;
}
if (state == DUNGEON_COMBAT_RESULT && action) {
if (combat_victory && startRoomEvent(&player_position)) return;
returnToRoom();
return;
}
if (state == DUNGEON_COMBAT && action) { resolveCombatAction(); return; }
if (state == DUNGEON_REWARD && action) {
returnToRoom();
return;
}
if (state == DUNGEON_ROOM && map_action) {
if (map_visible) {
map_visible = false;
waiting_for_centre = true;
returnToRoom();
return;
}
}
if (state == DUNGEON_ROOM && status_visible && context_action && status_page == 0U && getDungeonPlayerState()->unspentStatPoints > 0U) {
status_visible = false;
selected_stat_option = 0U;
state = DUNGEON_LEVEL_BONUS;
return;
}
if (state == DUNGEON_ROOM && !map_visible && !status_visible && context_action && isShopRoom(&getDungeonWorld()->floors[player_position.floor].cells[player_position.y][player_position.x])) {
selected_shop_option = 0U;
shop_text[0] = '\0';
state = DUNGEON_SHOP;
image_visible = false;
decode_pending = false;
return;
}
if (state == DUNGEON_ROOM && status_action) {
map_visible = false;
status_visible = !status_visible;
if (!status_visible) {
image_visible = false;
decode_pending = true;
}
waiting_for_centre = true;
return;
}
if (state == DUNGEON_ROOM && status_visible) {
if (action) status_page = (status_page + 1U) % 3U;
return;
}
if (state == DUNGEON_ROOM && map_visible) {
if (action) {
if (map_page == 0U) map_page = 1U;
else if (map_page == 1U) map_page = 3U;
else if (map_page == 3U) map_page = 2U;
else {
map_page = 0U;
map_floor = (map_floor + 1U) % DUNGEON_FLOOR_COUNT;
}
}
waiting_for_centre = true;
return;
}
if (state == DUNGEON_ROOM && map_action) {
map_floor = player_position.floor;
map_page = (player_position.y / 20U) * 2U + (player_position.x / 20U);
map_visible = true;
waiting_for_centre = true;
return;
}
if (!action) return;
if (state == DUNGEON_START_MENU && storage_result.error != DUNGEON_STORAGE_OK) {
traceDungeonCreation("A RECEIVED", 0, 0);
beginDungeonCreation(true, now_ms);
return;
}
if (state == DUNGEON_START_MENU && storage_result.error == DUNGEON_STORAGE_OK) {
if (selected_option == 0U) {
if (!prepareDungeonPlayer(&player_result)) {
state = DUNGEON_NOTICE;
return;
}
state = DUNGEON_ROOM;
player_position = getDungeonPlayerState()->position;
if (!prepareRoomImage(&player_position)) {
state = DUNGEON_NOTICE;
player_result.error = DUNGEON_PLAYER_INVALID_POSITION;
return;
}
if (!prepareDungeonExplored(&player_position, &explored_result)) {
state = DUNGEON_NOTICE;
return;
}
startPlayTimer(now_ms);
if (startRoomEvent(&player_position)) return;
image_visible = false;
decode_pending = true;
} else if (selected_option == 1U) {
beginDungeonCreation(false, now_ms);
return;
} else if (selected_option == 2U) {
ai_mode = true;
ai_start_at = now_ms;
ai_start_seconds = 2U;
state = DUNGEON_AI_START;
} else if (selected_option == 3U) {
if (!loadDungeonHistory(&history_result) && history_result.error == DUNGEON_HISTORY_OPEN_READ_FAILED) initDungeonHistory();
state = DUNGEON_HISTORY;
} else if (selected_option == 4U) {
menu_back(now_ms);
} else {
state = DUNGEON_NOTICE;
}
} else if (state == DUNGEON_NOTICE) {
state = DUNGEON_START_MENU;
}
}dungeonAIControl() · source line 1052
void dungeonAIControl(bool speed_action, uint32_t now_ms)
Calls into this documentation:
- Decision
!running || !ai_mode || state != DUNGEON_ROOM || map_visible || status_visibleTrue- Exit this function
return;
FalseContinue after this decision.
- Decision
speed_actionTrue- Execute
cycleAISpeed(&ai_speed_index); - Execute
ai_last_move = now_ms;
FalseContinue after this decision.
Read original function source
void dungeonAIControl(bool speed_action, uint32_t now_ms)
{
if (!running || !ai_mode || state != DUNGEON_ROOM || map_visible || status_visible) return;
if (speed_action) {
cycleAISpeed(&ai_speed_index);
ai_last_move = now_ms;
}
}updateDungeon() · source line 1064
void updateDungeon(uint16_t *frame, int x_offset, int y_offset, bool joystick_valid, uint32_t now_ms)
Calls into this documentation:
- Decision
!runningTrue- Exit this function
return;
FalseContinue after this decision.
- Execute
commitPlayTime(now_ms); - Decision
state == DUNGEON_AI_STARTTrue- Decision
!ai_modeTrue- Execute
state = DUNGEON_START_MENU; - Exit this function
return;
FalseContinue after this decision.
- Execute
const uint32_t elapsed = now_ms - ai_start_at; - Decision
elapsed < 2000UTrue- Execute
ai_start_seconds = (uint8_t)((2000U - elapsed + 999U) / 1000U); - Exit this function
return;
FalseContinue after this decision.
- Execute
state = DUNGEON_START_MENU; - Execute
selected_option = 1U; - Execute
dungeonAction(true, false, false, false, now_ms); - Exit this function
return;
FalseContinue after this decision.
- Decision
state == DUNGEON_CREATINGTrue- Execute
advanceDungeonCreation(now_ms); - Exit this function
return;
FalseContinue after this decision.
- Decision
decode_pendingTrue- Execute
decode_pending = false; - Execute
image_visible = imageDecodeFile(tile_path, frame);
FalseContinue after this decision.
- Decision
ai_mode && state == DUNGEON_ROOM && !map_visible && !status_visibleTrue- Decision
isShopRoom(&getDungeonWorld()->floors[player_position.floor].cells[player_position.y][player_position.x]) && !ai_shop_visitedTrue- Execute
ai_shop_visited = true; - Execute
dungeonAction(false, false, false, true, now_ms); - Exit this function
return;
FalseContinue after this decision.
- Decision
now_ms - ai_last_move >= aiSpeedDelay(ai_speed_index)True- Execute
ai_last_move = now_ms; - Execute
aiMoveOnce();
FalseContinue after this decision.
- Exit this function
return;
FalseContinue after this decision.
- Decision
ai_mode && (state == DUNGEON_REWARD || state == DUNGEON_TRAP_RESULT)True- Decision
now_ms - ai_last_move >= aiSpeedDelay(ai_speed_index)True- Execute
ai_last_move = now_ms; - Execute
dungeonAction(true, false, false, false, now_ms);
FalseContinue after this decision.
- Exit this function
return;
FalseContinue after this decision.
- Decision
ai_mode && state == DUNGEON_COMBATTrue- Decision
now_ms - ai_last_move >= aiSpeedDelay(ai_speed_index)True- Execute
ai_last_move = now_ms; - Execute
aiChooseCombatAction(); - Execute
dungeonAction(true, false, false, false, now_ms);
FalseContinue after this decision.
- Exit this function
return;
FalseContinue after this decision.
- Decision
ai_mode && state == DUNGEON_COMBAT_RESULTTrue- Decision
now_ms - ai_last_move >= aiSpeedDelay(ai_speed_index)True- Execute
ai_last_move = now_ms; - Execute
dungeonAction(true, false, false, false, now_ms);
FalseContinue after this decision.
- Exit this function
return;
FalseContinue after this decision.
- Decision
ai_mode && state == DUNGEON_LOCKED_DOORTrue- Decision
now_ms - ai_last_move >= aiSpeedDelay(ai_speed_index)True- Execute
ai_last_move = now_ms; - Execute
dungeonAction(true, false, false, false, now_ms);
FalseContinue after this decision.
- Exit this function
return;
FalseContinue after this decision.
- Decision
ai_mode && state == DUNGEON_SHOPTrue- Decision
now_ms - ai_last_move >= aiSpeedDelay(ai_speed_index)True- Execute
ai_last_move = now_ms; - Execute
selected_shop_option = aiChooseShopOption(); - Decision
selected_shop_option != 4UTrue- Execute
ai_last_shop_option = selected_shop_option;
FalseContinue after this decision.
- Execute
dungeonAction(true, false, selected_shop_option == 4U, false, now_ms);
FalseContinue after this decision.
- Exit this function
return;
FalseContinue after this decision.
- Decision
(state != DUNGEON_START_MENU && state != DUNGEON_ROOM && state != DUNGEON_COMBAT && state != DUNGEON_LEVEL_BONUS && state != DUNGEON_SHOP) || !joystick_valid || map_visible || status_visibleTrue- Exit this function
return;
FalseContinue after this decision.
- Execute
const int x_magnitude = x_offset < 0 ? -x_offset : x_offset; - Execute
const int y_magnitude = y_offset < 0 ? -y_offset : y_offset; - Decision
x_magnitude <= JOYSTICK_RELEASE && y_magnitude <= JOYSTICK_RELEASETrue- Execute
waiting_for_centre = false; - Execute
held_direction = 0; - Execute
repeating = false; - Exit this function
return;
FalseContinue after this decision.
- Decision
waiting_for_centreTrue- Exit this function
return;
FalseContinue after this decision.
- Decision
state == DUNGEON_ROOMTrue- Decision
x_magnitude < JOYSTICK_ENTER && y_magnitude < JOYSTICK_ENTERTrue- Exit this function
return;
FalseContinue after this decision.
- Decision
x_offset >= JOYSTICK_ENTER && y_magnitude >= JOYSTICK_ENTERTrue- Execute
moveStairs(y_offset > 0); - Execute
waiting_for_centre = true; - Exit this function
return;
FalseContinue after this decision.
- Execute
uint8_t exit; - Decision
x_magnitude >= y_magnitudeTrue- Execute
exit = x_offset > 0 ? DUNGEON_EXIT_EAST : DUNGEON_EXIT_WEST;
False- Execute
exit = y_offset > 0 ? DUNGEON_EXIT_NORTH : DUNGEON_EXIT_SOUTH;
- Execute
movePlayer(exit); - Execute
waiting_for_centre = true; - Exit this function
return;
FalseContinue after this decision.
- Decision
y_magnitude < JOYSTICK_ENTERTrue- Exit this function
return;
FalseContinue after this decision.
- Execute
const int direction = y_offset > 0 ? -1 : 1; - Decision
direction == held_direction && now_ms - last_move < (repeating ? REPEAT_MS : REPEAT_START_MS)True- Exit this function
return;
FalseContinue after this decision.
- Execute
repeating = direction == held_direction; - Execute
held_direction = direction; - Execute
last_move = now_ms; - Decision
state == DUNGEON_COMBATTrue- Execute
const unsigned count = combatOptionCount(); - Decision
direction < 0 && selected_combat_option > 0UTrue- Execute
--selected_combat_option;
FalseContinue after this decision.
- Decision
direction > 0 && selected_combat_option + 1U < countTrue- Execute
++selected_combat_option;
FalseContinue after this decision.
- Exit this function
return;
FalseContinue after this decision.
- Decision
state == DUNGEON_LEVEL_BONUSTrue- Decision
direction < 0 && selected_stat_option > 0UTrue- Execute
--selected_stat_option;
FalseContinue after this decision.
- Decision
direction > 0 && selected_stat_option < 2UTrue- Execute
++selected_stat_option;
FalseContinue after this decision.
- Exit this function
return;
FalseContinue after this decision.
- Decision
state == DUNGEON_SHOPTrue- Decision
direction < 0 && selected_shop_option > 0UTrue- Execute
--selected_shop_option;
FalseContinue after this decision.
- Decision
direction > 0 && selected_shop_option < 4UTrue- Execute
++selected_shop_option;
FalseContinue after this decision.
- Exit this function
return;
FalseContinue after this decision.
- Execute
const unsigned count = sizeof(start_options) / sizeof(start_options[0]); - Decision
direction < 0 && selected_option > 0UTrue- Execute
--selected_option;
FalseContinue after this decision.
- Decision
direction > 0 && selected_option + 1U < countTrue- Execute
++selected_option;
FalseContinue after this decision.
Read original function source
void updateDungeon(uint16_t *frame, int x_offset, int y_offset, bool joystick_valid, uint32_t now_ms)
{
if (!running) return;
commitPlayTime(now_ms);
if (state == DUNGEON_AI_START) {
if (!ai_mode) {
state = DUNGEON_START_MENU;
return;
}
const uint32_t elapsed = now_ms - ai_start_at;
if (elapsed < 2000U) {
ai_start_seconds = (uint8_t)((2000U - elapsed + 999U) / 1000U);
return;
}
state = DUNGEON_START_MENU;
selected_option = 1U;
dungeonAction(true, false, false, false, now_ms);
return;
}
if (state == DUNGEON_CREATING) {
advanceDungeonCreation(now_ms);
return;
}
if (decode_pending) {
decode_pending = false;
image_visible = imageDecodeFile(tile_path, frame);
}
if (ai_mode && state == DUNGEON_ROOM && !map_visible && !status_visible) {
if (isShopRoom(&getDungeonWorld()->floors[player_position.floor].cells[player_position.y][player_position.x]) && !ai_shop_visited) {
ai_shop_visited = true;
dungeonAction(false, false, false, true, now_ms);
return;
}
if (now_ms - ai_last_move >= aiSpeedDelay(ai_speed_index)) {
ai_last_move = now_ms;
aiMoveOnce();
}
return;
}
if (ai_mode && (state == DUNGEON_REWARD || state == DUNGEON_TRAP_RESULT)) {
if (now_ms - ai_last_move >= aiSpeedDelay(ai_speed_index)) {
ai_last_move = now_ms;
dungeonAction(true, false, false, false, now_ms);
}
return;
}
if (ai_mode && state == DUNGEON_COMBAT) {
if (now_ms - ai_last_move >= aiSpeedDelay(ai_speed_index)) {
ai_last_move = now_ms;
aiChooseCombatAction();
dungeonAction(true, false, false, false, now_ms);
}
return;
}
if (ai_mode && state == DUNGEON_COMBAT_RESULT) {
if (now_ms - ai_last_move >= aiSpeedDelay(ai_speed_index)) {
ai_last_move = now_ms;
dungeonAction(true, false, false, false, now_ms);
}
return;
}
if (ai_mode && state == DUNGEON_LOCKED_DOOR) {
if (now_ms - ai_last_move >= aiSpeedDelay(ai_speed_index)) {
ai_last_move = now_ms;
dungeonAction(true, false, false, false, now_ms);
}
return;
}
if (ai_mode && state == DUNGEON_SHOP) {
if (now_ms - ai_last_move >= aiSpeedDelay(ai_speed_index)) {
ai_last_move = now_ms;
selected_shop_option = aiChooseShopOption();
if (selected_shop_option != 4U) ai_last_shop_option = selected_shop_option;
dungeonAction(true, false, selected_shop_option == 4U, false, now_ms);
}
return;
}
if ((state != DUNGEON_START_MENU && state != DUNGEON_ROOM && state != DUNGEON_COMBAT && state != DUNGEON_LEVEL_BONUS && state != DUNGEON_SHOP) || !joystick_valid || map_visible || status_visible) return;
const int x_magnitude = x_offset < 0 ? -x_offset : x_offset;
const int y_magnitude = y_offset < 0 ? -y_offset : y_offset;
if (x_magnitude <= JOYSTICK_RELEASE && y_magnitude <= JOYSTICK_RELEASE) {
waiting_for_centre = false;
held_direction = 0;
repeating = false;
return;
}
if (waiting_for_centre) return;
if (state == DUNGEON_ROOM) {
if (x_magnitude < JOYSTICK_ENTER && y_magnitude < JOYSTICK_ENTER) return;
if (x_offset >= JOYSTICK_ENTER && y_magnitude >= JOYSTICK_ENTER) {
moveStairs(y_offset > 0);
waiting_for_centre = true;
return;
}
uint8_t exit;
if (x_magnitude >= y_magnitude) exit = x_offset > 0 ? DUNGEON_EXIT_EAST : DUNGEON_EXIT_WEST;
else exit = y_offset > 0 ? DUNGEON_EXIT_NORTH : DUNGEON_EXIT_SOUTH;
movePlayer(exit);
waiting_for_centre = true;
return;
}
if (y_magnitude < JOYSTICK_ENTER) return;
const int direction = y_offset > 0 ? -1 : 1;
if (direction == held_direction && now_ms - last_move < (repeating ? REPEAT_MS : REPEAT_START_MS)) return;
repeating = direction == held_direction;
held_direction = direction;
last_move = now_ms;
if (state == DUNGEON_COMBAT) {
const unsigned count = combatOptionCount();
if (direction < 0 && selected_combat_option > 0U) --selected_combat_option;
if (direction > 0 && selected_combat_option + 1U < count) ++selected_combat_option;
return;
}
if (state == DUNGEON_LEVEL_BONUS) {
if (direction < 0 && selected_stat_option > 0U) --selected_stat_option;
if (direction > 0 && selected_stat_option < 2U) ++selected_stat_option;
return;
}
if (state == DUNGEON_SHOP) {
if (direction < 0 && selected_shop_option > 0U) --selected_shop_option;
if (direction > 0 && selected_shop_option < 4U) ++selected_shop_option;
return;
}
const unsigned count = sizeof(start_options) / sizeof(start_options[0]);
if (direction < 0 && selected_option > 0U) --selected_option;
if (direction > 0 && selected_option + 1U < count) ++selected_option;
}drawBoldLine() · source line 1197
static void drawBoldLine(uint16_t *frame, int x0, int y0, int x1, int y1, uint16_t color)
Calls into this documentation:
- Execute
draw_line(frame, x0, y0, x1, y1, color); - Execute
draw_line(frame, x0 + 1, y0, x1 + 1, y1, color); - Execute
draw_line(frame, x0, y0 + 1, x1, y1 + 1, color);
Read original function source
static void drawBoldLine(uint16_t *frame, int x0, int y0, int x1, int y1, uint16_t color)
{
draw_line(frame, x0, y0, x1, y1, color);
draw_line(frame, x0 + 1, y0, x1 + 1, y1, color);
draw_line(frame, x0, y0 + 1, x1, y1 + 1, color);
}exitDestinationExplored() · source line 1204
static bool exitDestinationExplored(uint8_t exit)
Calls into this documentation:
- Execute
int x = player_position.x; - Execute
int y = player_position.y; - Decision
exit == DUNGEON_EXIT_NORTHTrue- Execute
--y;
False- Decision
exit == DUNGEON_EXIT_EASTTrue- Execute
++x;
False- Decision
exit == DUNGEON_EXIT_SOUTHTrue- Execute
++y;
False- Decision
exit == DUNGEON_EXIT_WESTTrue- Execute
--x;
FalseContinue after this decision.
- Exit this function
return x >= 0 && x < (int)DUNGEON_WIDTH && y >= 0 && y < (int)DUNGEON_HEIGHT && isDungeonExplored(player_position.floor, (uint8_t)x, (uint8_t)y);
Read original function source
static bool exitDestinationExplored(uint8_t exit)
{
int x = player_position.x;
int y = player_position.y;
if (exit == DUNGEON_EXIT_NORTH) --y;
else if (exit == DUNGEON_EXIT_EAST) ++x;
else if (exit == DUNGEON_EXIT_SOUTH) ++y;
else if (exit == DUNGEON_EXIT_WEST) --x;
return x >= 0 && x < (int)DUNGEON_WIDTH && y >= 0 && y < (int)DUNGEON_HEIGHT &&
isDungeonExplored(player_position.floor, (uint8_t)x, (uint8_t)y);
}drawExitIcon() · source line 1216
static void drawExitIcon(uint16_t *frame, uint8_t exit, uint16_t color)
Calls into this documentation:
- Execute
int x = 160; - Execute
int y = 85; - Decision
exit == DUNGEON_EXIT_NORTHTrue- Execute
y = 12;
False- Decision
exit == DUNGEON_EXIT_EASTTrue- Execute
x = LCD_WIDTH - 13;
False- Decision
exit == DUNGEON_EXIT_SOUTHTrue- Execute
y = 157;
False- Decision
exit == DUNGEON_EXIT_WESTTrue- Execute
x = 12;
FalseContinue after this decision.
- Decision
exit == DUNGEON_EXIT_NORTH || exit == DUNGEON_EXIT_SOUTHTrue- Execute
const int direction = exit == DUNGEON_EXIT_NORTH ? -1 : 1; - Execute
drawBoldLine(frame, x, y - direction * 7, x, y + direction * 7, color); - Execute
drawBoldLine(frame, x, y + direction * 7, x - 5, y + direction * 2, color); - Execute
drawBoldLine(frame, x, y + direction * 7, x + 5, y + direction * 2, color);
False- Execute
const int direction = exit == DUNGEON_EXIT_EAST ? 1 : -1; - Execute
drawBoldLine(frame, x - direction * 7, y, x + direction * 7, y, color); - Execute
drawBoldLine(frame, x + direction * 7, y, x + direction * 2, y - 5, color); - Execute
drawBoldLine(frame, x + direction * 7, y, x + direction * 2, y + 5, color);
Read original function source
static void drawExitIcon(uint16_t *frame, uint8_t exit, uint16_t color)
{
int x = 160;
int y = 85;
if (exit == DUNGEON_EXIT_NORTH) y = 12;
else if (exit == DUNGEON_EXIT_EAST) x = LCD_WIDTH - 13;
else if (exit == DUNGEON_EXIT_SOUTH) y = 157;
else if (exit == DUNGEON_EXIT_WEST) x = 12;
if (exit == DUNGEON_EXIT_NORTH || exit == DUNGEON_EXIT_SOUTH) {
const int direction = exit == DUNGEON_EXIT_NORTH ? -1 : 1;
drawBoldLine(frame, x, y - direction * 7, x, y + direction * 7, color);
drawBoldLine(frame, x, y + direction * 7, x - 5, y + direction * 2, color);
drawBoldLine(frame, x, y + direction * 7, x + 5, y + direction * 2, color);
} else {
const int direction = exit == DUNGEON_EXIT_EAST ? 1 : -1;
drawBoldLine(frame, x - direction * 7, y, x + direction * 7, y, color);
drawBoldLine(frame, x + direction * 7, y, x + direction * 2, y - 5, color);
drawBoldLine(frame, x + direction * 7, y, x + direction * 2, y + 5, color);
}
}drawStairIcon() · source line 1238
static void drawStairIcon(uint16_t *frame, bool up)
Calls into this documentation:
- Execute
const int x = LCD_WIDTH - 44; - Execute
const int y = up ? 12 : 157; - Execute
const int direction = up ? -1 : 1; - Execute
drawBoldLine(frame, x, y - direction * 7, x, y + direction * 7, COLOR_YELLOW); - Execute
drawBoldLine(frame, x, y + direction * 7, x - 5, y + direction * 2, COLOR_YELLOW); - Execute
drawBoldLine(frame, x, y + direction * 7, x + 5, y + direction * 2, COLOR_YELLOW);
Read original function source
static void drawStairIcon(uint16_t *frame, bool up)
{
const int x = LCD_WIDTH - 44;
const int y = up ? 12 : 157;
const int direction = up ? -1 : 1;
drawBoldLine(frame, x, y - direction * 7, x, y + direction * 7, COLOR_YELLOW);
drawBoldLine(frame, x, y + direction * 7, x - 5, y + direction * 2, COLOR_YELLOW);
drawBoldLine(frame, x, y + direction * 7, x + 5, y + direction * 2, COLOR_YELLOW);
}drawShopIndicator() · source line 1248
static void drawShopIndicator(uint16_t *frame)
Calls into this documentation:
- Execute
draw_text(frame, 5, 5, "C SHOP", 1, COLOR_YELLOW);
Read original function source
static void drawShopIndicator(uint16_t *frame)
{
draw_text(frame, 5, 5, "C SHOP", 1, COLOR_YELLOW);
}mapCellColor() · source line 1253
static uint16_t mapCellColor(const DungeonCell *cell)
- Decision
cell->tileType == DUNGEON_TILE_STAIRS_UP || cell->tileType == DUNGEON_TILE_STAIRS_DOWN || cell->tileType == DUNGEON_TILE_STAIRS_BOTHTrue- Exit this function
return COLOR_PURPLE;
FalseContinue after this decision.
- Decision
cell->tileType == DUNGEON_TILE_ENTRANCETrue- Exit this function
return COLOR_GREEN;
FalseContinue after this decision.
- Decision
cell->tileType == DUNGEON_TILE_EXITTrue- Exit this function
return COLOR_MAGENTA;
FalseContinue after this decision.
- Decision
cell->tileType == DUNGEON_TILE_PASSAGE || cell->tileType == DUNGEON_TILE_DEAD_END_PASSAGETrue- Exit this function
return COLOR_LIGHTGRAY;
FalseContinue after this decision.
- Exit this function
return COLOR_CYAN;
Read original function source
static uint16_t mapCellColor(const DungeonCell *cell)
{
if (cell->tileType == DUNGEON_TILE_STAIRS_UP || cell->tileType == DUNGEON_TILE_STAIRS_DOWN || cell->tileType == DUNGEON_TILE_STAIRS_BOTH) return COLOR_PURPLE;
if (cell->tileType == DUNGEON_TILE_ENTRANCE) return COLOR_GREEN;
if (cell->tileType == DUNGEON_TILE_EXIT) return COLOR_MAGENTA;
if (cell->tileType == DUNGEON_TILE_PASSAGE || cell->tileType == DUNGEON_TILE_DEAD_END_PASSAGE) return COLOR_LIGHTGRAY;
return COLOR_CYAN;
}drawMapCell() · source line 1265
static void drawMapCell(uint16_t *frame, unsigned x, unsigned y, unsigned first_column, unsigned first_row, const DungeonCell *cell)
Calls into this documentation:
- Execute
const int screen_x = ((int)x - (int)first_column) * 8; - Execute
const int screen_y = ((int)y - (int)first_row) * 8; - Execute
const uint16_t color = mapCellColor(cell); - Execute
const bool large = cell->tileType != DUNGEON_TILE_PASSAGE && cell->tileType != DUNGEON_TILE_DEAD_END_PASSAGE; - Execute
fill_rect(frame, screen_x + (large ? 1 : 2), screen_y + (large ? 1 : 2), large ? 6 : 4, large ? 6 : 4, color); - Decision
(cell->exits & DUNGEON_EXIT_NORTH) != 0UTrue- Execute
const bool known = y > 0U && isDungeonExplored((uint8_t)map_floor, (uint8_t)x, (uint8_t)(y - 1U)); - Execute
fill_rect(frame, screen_x + 3, screen_y, 2, 4, known ? color : COLOR_ORANGE); - Decision
!knownTrue- Execute
fill_rect(frame, screen_x + 2, screen_y, 4, 2, COLOR_ORANGE);
FalseContinue after this decision.
FalseContinue after this decision.
- Decision
(cell->exits & DUNGEON_EXIT_EAST) != 0UTrue- Execute
const bool known = x + 1U < DUNGEON_WIDTH && isDungeonExplored((uint8_t)map_floor, (uint8_t)(x + 1U), (uint8_t)y); - Execute
fill_rect(frame, screen_x + 4, screen_y + 3, 4, 2, known ? color : COLOR_ORANGE); - Decision
!knownTrue- Execute
fill_rect(frame, screen_x + 6, screen_y + 2, 2, 4, COLOR_ORANGE);
FalseContinue after this decision.
FalseContinue after this decision.
- Decision
(cell->exits & DUNGEON_EXIT_SOUTH) != 0UTrue- Execute
const bool known = y + 1U < DUNGEON_HEIGHT && isDungeonExplored((uint8_t)map_floor, (uint8_t)x, (uint8_t)(y + 1U)); - Execute
fill_rect(frame, screen_x + 3, screen_y + 4, 2, 4, known ? color : COLOR_ORANGE); - Decision
!knownTrue- Execute
fill_rect(frame, screen_x + 2, screen_y + 6, 4, 2, COLOR_ORANGE);
FalseContinue after this decision.
FalseContinue after this decision.
- Decision
(cell->exits & DUNGEON_EXIT_WEST) != 0UTrue- Execute
const bool known = x > 0U && isDungeonExplored((uint8_t)map_floor, (uint8_t)(x - 1U), (uint8_t)y); - Execute
fill_rect(frame, screen_x, screen_y + 3, 4, 2, known ? color : COLOR_ORANGE); - Decision
!knownTrue- Execute
fill_rect(frame, screen_x, screen_y + 2, 2, 4, COLOR_ORANGE);
FalseContinue after this decision.
FalseContinue after this decision.
Read original function source
static void drawMapCell(uint16_t *frame, unsigned x, unsigned y, unsigned first_column, unsigned first_row, const DungeonCell *cell)
{
const int screen_x = ((int)x - (int)first_column) * 8;
const int screen_y = ((int)y - (int)first_row) * 8;
const uint16_t color = mapCellColor(cell);
const bool large = cell->tileType != DUNGEON_TILE_PASSAGE && cell->tileType != DUNGEON_TILE_DEAD_END_PASSAGE;
fill_rect(frame, screen_x + (large ? 1 : 2), screen_y + (large ? 1 : 2), large ? 6 : 4, large ? 6 : 4, color);
if ((cell->exits & DUNGEON_EXIT_NORTH) != 0U) {
const bool known = y > 0U && isDungeonExplored((uint8_t)map_floor, (uint8_t)x, (uint8_t)(y - 1U));
fill_rect(frame, screen_x + 3, screen_y, 2, 4, known ? color : COLOR_ORANGE);
if (!known) fill_rect(frame, screen_x + 2, screen_y, 4, 2, COLOR_ORANGE);
}
if ((cell->exits & DUNGEON_EXIT_EAST) != 0U) {
const bool known = x + 1U < DUNGEON_WIDTH && isDungeonExplored((uint8_t)map_floor, (uint8_t)(x + 1U), (uint8_t)y);
fill_rect(frame, screen_x + 4, screen_y + 3, 4, 2, known ? color : COLOR_ORANGE);
if (!known) fill_rect(frame, screen_x + 6, screen_y + 2, 2, 4, COLOR_ORANGE);
}
if ((cell->exits & DUNGEON_EXIT_SOUTH) != 0U) {
const bool known = y + 1U < DUNGEON_HEIGHT && isDungeonExplored((uint8_t)map_floor, (uint8_t)x, (uint8_t)(y + 1U));
fill_rect(frame, screen_x + 3, screen_y + 4, 2, 4, known ? color : COLOR_ORANGE);
if (!known) fill_rect(frame, screen_x + 2, screen_y + 6, 4, 2, COLOR_ORANGE);
}
if ((cell->exits & DUNGEON_EXIT_WEST) != 0U) {
const bool known = x > 0U && isDungeonExplored((uint8_t)map_floor, (uint8_t)(x - 1U), (uint8_t)y);
fill_rect(frame, screen_x, screen_y + 3, 4, 2, known ? color : COLOR_ORANGE);
if (!known) fill_rect(frame, screen_x, screen_y + 2, 2, 4, COLOR_ORANGE);
}
}drawMapKeyItem() · source line 1294
static void drawMapKeyItem(uint16_t *frame, int y, uint16_t color, const char *label)
Calls into this documentation:
- Execute
fill_rect(frame, 174, y, 10, 10, color); - Execute
drawPet2001TextTransparent(frame, 190, y + 1, label, COLOR_WHITE, 1);
Read original function source
static void drawMapKeyItem(uint16_t *frame, int y, uint16_t color, const char *label)
{
fill_rect(frame, 174, y, 10, 10, color);
drawPet2001TextTransparent(frame, 190, y + 1, label, COLOR_WHITE, 1);
}drawMapSidePanel() · source line 1300
static void drawMapSidePanel(uint16_t *frame)
Calls into this documentation:
- Execute
draw_line(frame, 164, 0, 164, 160, COLOR_DARKGRAY); - Execute
drawPet2001TextTransparent(frame, 174, 5, "KEY", COLOR_CYAN, 2); - Execute
drawMapKeyItem(frame, 31, COLOR_YELLOW, "PLR"); - Execute
drawMapKeyItem(frame, 47, COLOR_LIGHTGRAY, "PATH"); - Execute
drawMapKeyItem(frame, 63, COLOR_CYAN, "ROOM"); - Execute
drawMapKeyItem(frame, 79, COLOR_ORANGE, "UNEXP EXIT"); - Execute
drawMapKeyItem(frame, 95, COLOR_PURPLE, "STAIRS"); - Execute
drawMapKeyItem(frame, 111, COLOR_GREEN, "ENTRANCE"); - Execute
drawMapKeyItem(frame, 127, COLOR_RED, "KEY"); - Execute
drawPet2001TextCentredTransparent(frame, 247, 3, "N", COLOR_WHITE, 1); - Execute
drawPet2001TextCentredTransparent(frame, 247, 35, "S", COLOR_WHITE, 1); - Execute
drawPet2001TextCentredTransparent(frame, 232, 19, "W", COLOR_WHITE, 1); - Execute
drawPet2001TextCentredTransparent(frame, 262, 19, "E", COLOR_WHITE, 1); - Execute
draw_line(frame, 239, 23, 255, 23, COLOR_LIGHTGRAY); - Execute
draw_line(frame, 247, 15, 247, 31, COLOR_LIGHTGRAY); - Execute
draw_line(frame, 247, 13, 243, 20, COLOR_RED); - Execute
draw_line(frame, 247, 13, 251, 20, COLOR_RED); - Execute
draw_rect(frame, 270, 5, 40, 40, COLOR_WHITE); - Execute
draw_line(frame, 290, 5, 290, 45, COLOR_WHITE); - Execute
draw_line(frame, 270, 25, 310, 25, COLOR_WHITE); - FOR loop
unsigned page = 0; page < 4U; ++pageRepeat body while the loop condition permits; then continue below.
- Execute
const int x = 270 + (int)(page % 2U) * 20; - Execute
const int y = 5 + (int)(page / 2U) * 20; - Execute
const bool selected = page == map_page; - Execute
fill_rect(frame, x + 2, y + 2, 17, 17, selected ? COLOR_YELLOW : COLOR_DARKGRAY); - Execute
char number[2] = { (char)('1' + page), '\0' }; - Execute
drawPet2001TextCentredTransparent(frame, x + 10, y + 6, number, selected ? COLOR_BLACK : COLOR_WHITE, 1);
- Execute
const unsigned player_page = (player_position.y / 20U) * 2U + player_position.x / 20U; - Decision
player_position.floor == map_floor && player_page != map_pageTrue- Execute
const int x = 270 + (int)(player_page % 2U) * 20; - Execute
const int y = 5 + (int)(player_page / 2U) * 20; - Execute
fill_circle(frame, x + 15, y + 15, 2, COLOR_YELLOW);
FalseContinue after this decision.
Read original function source
static void drawMapSidePanel(uint16_t *frame)
{
draw_line(frame, 164, 0, 164, 160, COLOR_DARKGRAY);
drawPet2001TextTransparent(frame, 174, 5, "KEY", COLOR_CYAN, 2);
drawMapKeyItem(frame, 31, COLOR_YELLOW, "PLR");
drawMapKeyItem(frame, 47, COLOR_LIGHTGRAY, "PATH");
drawMapKeyItem(frame, 63, COLOR_CYAN, "ROOM");
drawMapKeyItem(frame, 79, COLOR_ORANGE, "UNEXP EXIT");
drawMapKeyItem(frame, 95, COLOR_PURPLE, "STAIRS");
drawMapKeyItem(frame, 111, COLOR_GREEN, "ENTRANCE");
drawMapKeyItem(frame, 127, COLOR_RED, "KEY");
drawPet2001TextCentredTransparent(frame, 247, 3, "N", COLOR_WHITE, 1);
drawPet2001TextCentredTransparent(frame, 247, 35, "S", COLOR_WHITE, 1);
drawPet2001TextCentredTransparent(frame, 232, 19, "W", COLOR_WHITE, 1);
drawPet2001TextCentredTransparent(frame, 262, 19, "E", COLOR_WHITE, 1);
draw_line(frame, 239, 23, 255, 23, COLOR_LIGHTGRAY);
draw_line(frame, 247, 15, 247, 31, COLOR_LIGHTGRAY);
draw_line(frame, 247, 13, 243, 20, COLOR_RED);
draw_line(frame, 247, 13, 251, 20, COLOR_RED);
draw_rect(frame, 270, 5, 40, 40, COLOR_WHITE);
draw_line(frame, 290, 5, 290, 45, COLOR_WHITE);
draw_line(frame, 270, 25, 310, 25, COLOR_WHITE);
for (unsigned page = 0; page < 4U; ++page) {
const int x = 270 + (int)(page % 2U) * 20;
const int y = 5 + (int)(page / 2U) * 20;
const bool selected = page == map_page;
fill_rect(frame, x + 2, y + 2, 17, 17, selected ? COLOR_YELLOW : COLOR_DARKGRAY);
char number[2] = { (char)('1' + page), '\0' };
drawPet2001TextCentredTransparent(frame, x + 10, y + 6, number,
selected ? COLOR_BLACK : COLOR_WHITE, 1);
}
const unsigned player_page = (player_position.y / 20U) * 2U + player_position.x / 20U;
if (player_position.floor == map_floor && player_page != map_page) {
const int x = 270 + (int)(player_page % 2U) * 20;
const int y = 5 + (int)(player_page / 2U) * 20;
fill_circle(frame, x + 15, y + 15, 2, COLOR_YELLOW);
}
}drawDungeonMap() · source line 1341
static void drawDungeonMap(uint16_t *frame)
Calls into this documentation:
- Execute
const unsigned first_column = (map_page % 2U) * 20U; - Execute
const unsigned first_row = (map_page / 2U) * 20U; - Execute
const DungeonFloor *floor = &getDungeonWorld()->floors[map_floor]; - Execute
clear(frame, COLOR_BLACK); - FOR loop
unsigned y = first_row; y < first_row + 20U; ++yRepeat body while the loop condition permits; then continue below.
- FOR loop
unsigned x = first_column; x < first_column + 20U; ++xRepeat body while the loop condition permits; then continue below.
- Decision
isDungeonExplored((uint8_t)map_floor, (uint8_t)x, (uint8_t)y)True- Execute
drawMapCell(frame, x, y, first_column, first_row, &floor->cells[y][x]);
FalseContinue after this decision.
- Decision
(getDungeonPlayerState()->runFlags & DUNGEON_RUN_KEY_REVEALED) != 0UTrue- Execute
bool key_drawn = false; - FOR loop
unsigned y = 0; y < DUNGEON_HEIGHT && !key_drawn; ++yRepeat body while the loop condition permits; then continue below.
- FOR loop
unsigned x = 0; x < DUNGEON_WIDTH; ++xRepeat body while the loop condition permits; then continue below.
- Execute
const DungeonCell *cell = &floor->cells[y][x]; - Decision
(cell->eventFlags & DUNGEON_EVENT_KEY) == 0U || (cell->stateFlags & DUNGEON_STATE_KEY_COLLECTED) != 0UTrue- Next loop iteration
continue;
FalseContinue after this decision.
- Decision
x >= first_column && x < first_column + 20U && y >= first_row && y < first_row + 20UTrue- Execute
fill_rect(frame, ((int)x - (int)first_column) * 8 + 1, ((int)y - (int)first_row) * 8 + 1, 6, 6, COLOR_RED);
FalseContinue after this decision.
- Execute
key_drawn = true; - Exit enclosing loop or switch
break;
FalseContinue after this decision.
- Decision
player_position.floor == map_floor && player_position.x >= first_column && player_position.x < first_column + 20U && player_position.y >= first_row && player_position.y < first_row + 20UTrue- Execute
fill_rect(frame, ((int)player_position.x - (int)first_column) * 8 + 1, ((int)player_position.y - (int)first_row) * 8 + 1, 6, 6, COLOR_YELLOW);
FalseContinue after this decision.
- Execute
drawMapSidePanel(frame); - Execute
char footer[20]; - Execute
snprintf(footer, sizeof(footer), "FLOOR %u PAGE %u/4", map_floor + 1U, map_page + 1U); - Execute
drawPet2001TextTransparent(frame, 4, 161, footer, COLOR_WHITE, 1); - Execute
drawPet2001TextTransparent(frame, 172, 161, "A/K PAGE C CLOSE", COLOR_LIGHTGRAY, 1);
Read original function source
static void drawDungeonMap(uint16_t *frame)
{
const unsigned first_column = (map_page % 2U) * 20U;
const unsigned first_row = (map_page / 2U) * 20U;
const DungeonFloor *floor = &getDungeonWorld()->floors[map_floor];
clear(frame, COLOR_BLACK);
for (unsigned y = first_row; y < first_row + 20U; ++y)
for (unsigned x = first_column; x < first_column + 20U; ++x)
if (isDungeonExplored((uint8_t)map_floor, (uint8_t)x, (uint8_t)y)) drawMapCell(frame, x, y, first_column, first_row, &floor->cells[y][x]);
if ((getDungeonPlayerState()->runFlags & DUNGEON_RUN_KEY_REVEALED) != 0U) {
bool key_drawn = false;
for (unsigned y = 0; y < DUNGEON_HEIGHT && !key_drawn; ++y) {
for (unsigned x = 0; x < DUNGEON_WIDTH; ++x) {
const DungeonCell *cell = &floor->cells[y][x];
if ((cell->eventFlags & DUNGEON_EVENT_KEY) == 0U ||
(cell->stateFlags & DUNGEON_STATE_KEY_COLLECTED) != 0U) continue;
if (x >= first_column && x < first_column + 20U &&
y >= first_row && y < first_row + 20U)
fill_rect(frame, ((int)x - (int)first_column) * 8 + 1,
((int)y - (int)first_row) * 8 + 1, 6, 6, COLOR_RED);
key_drawn = true;
break;
}
}
}
if (player_position.floor == map_floor && player_position.x >= first_column && player_position.x < first_column + 20U && player_position.y >= first_row && player_position.y < first_row + 20U)
fill_rect(frame, ((int)player_position.x - (int)first_column) * 8 + 1, ((int)player_position.y - (int)first_row) * 8 + 1, 6, 6, COLOR_YELLOW);
drawMapSidePanel(frame);
char footer[20];
snprintf(footer, sizeof(footer), "FLOOR %u PAGE %u/4", map_floor + 1U, map_page + 1U);
drawPet2001TextTransparent(frame, 4, 161, footer, COLOR_WHITE, 1);
drawPet2001TextTransparent(frame, 172, 161, "A/K PAGE C CLOSE", COLOR_LIGHTGRAY, 1);
}isRoomTile() · source line 1375
static bool isRoomTile(uint8_t tile_type)
- Exit this function
return tile_type == DUNGEON_TILE_ROOM_OPEN || tile_type == DUNGEON_TILE_DEAD_END_ROOM_ROUND || tile_type == DUNGEON_TILE_DEAD_END_ROOM_SQUARE || tile_type == DUNGEON_TILE_DEAD_END_ROOM_ROUND_ALT;
Read original function source
static bool isRoomTile(uint8_t tile_type)
{
return tile_type == DUNGEON_TILE_ROOM_OPEN ||
tile_type == DUNGEON_TILE_DEAD_END_ROOM_ROUND ||
tile_type == DUNGEON_TILE_DEAD_END_ROOM_SQUARE ||
tile_type == DUNGEON_TILE_DEAD_END_ROOM_ROUND_ALT;
}exploredRoomCount() · source line 1383
static unsigned exploredRoomCount(unsigned floor_index, unsigned *total_rooms)
Calls into this documentation:
- Execute
unsigned explored = 0; - Execute
unsigned total = 0; - FOR loop
unsigned y = 0; y < DUNGEON_HEIGHT; ++yRepeat body while the loop condition permits; then continue below.
- FOR loop
unsigned x = 0; x < DUNGEON_WIDTH; ++xRepeat body while the loop condition permits; then continue below.
- Decision
isRoomTile(getDungeonWorld()->floors[floor_index].cells[y][x].tileType)True- Execute
++total; - Decision
isDungeonExplored((uint8_t)floor_index, (uint8_t)x, (uint8_t)y)True- Execute
++explored;
FalseContinue after this decision.
FalseContinue after this decision.
- Decision
total_rooms != NULLTrue- Execute
*total_rooms = total;
FalseContinue after this decision.
- Exit this function
return explored;
Read original function source
static unsigned exploredRoomCount(unsigned floor_index, unsigned *total_rooms)
{
unsigned explored = 0;
unsigned total = 0;
for (unsigned y = 0; y < DUNGEON_HEIGHT; ++y)
for (unsigned x = 0; x < DUNGEON_WIDTH; ++x)
if (isRoomTile(getDungeonWorld()->floors[floor_index].cells[y][x].tileType)) {
++total;
if (isDungeonExplored((uint8_t)floor_index, (uint8_t)x, (uint8_t)y)) ++explored;
}
if (total_rooms != NULL) *total_rooms = total;
return explored;
}specialItemCount() · source line 1397
static unsigned specialItemCount(uint32_t flags)
- Execute
unsigned count = 0; - WHILE loop
flags != 0URepeat body while the loop condition permits; then continue below.
- Execute
count += flags & 1U; - Execute
flags >>= 1;
- Exit this function
return count;
Read original function source
static unsigned specialItemCount(uint32_t flags)
{
unsigned count = 0;
while (flags != 0U) {
count += flags & 1U;
flags >>= 1;
}
return count;
}formatPlayedTime() · source line 1407
static void formatPlayedTime(char *text, size_t size, uint32_t total_seconds)
- Execute
const uint32_t hours = total_seconds / 3600U; - Execute
const uint32_t minutes = (total_seconds / 60U) % 60U; - Execute
const uint32_t seconds = total_seconds % 60U; - Execute
snprintf(text, size, "%lu:%02lu:%02lu", (unsigned long)hours, (unsigned long)minutes, (unsigned long)seconds);
Read original function source
static void formatPlayedTime(char *text, size_t size, uint32_t total_seconds)
{
const uint32_t hours = total_seconds / 3600U;
const uint32_t minutes = (total_seconds / 60U) % 60U;
const uint32_t seconds = total_seconds % 60U;
snprintf(text, size, "%lu:%02lu:%02lu", (unsigned long)hours,
(unsigned long)minutes, (unsigned long)seconds);
}drawStatusScreen() · source line 1419
static void drawStatusScreen(uint16_t *frame)
Calls into this documentation:
- Execute
const DungeonPlayerState *player = getDungeonPlayerState(); - Execute
char text[40]; - Execute
clear(frame, COLOR_BLACK); - Execute
draw_text_centred(frame, 160, 5, status_page == 0U ? "STATUS" : status_page == 1U ? "INVENTORY" : "PROGRESS", 2, COLOR_CYAN); - Execute
snprintf(text, sizeof(text), "%u/3", status_page + 1U); - Execute
draw_text(frame, 292, 7, text, 1, COLOR_DARKGRAY); - Decision
status_page == 0UTrue- Execute
snprintf(text, sizeof(text), "Lvl %u XP %lu/%lu", (unsigned)player->level, (unsigned long)player->experience, (unsigned long)dungeonExperienceNeeded(player->level)); - Execute
draw_text(frame, 12, 35, text, 2, COLOR_WHITE); - Execute
snprintf(text, sizeof(text), "Health: %u/%u", (unsigned)player->health, (unsigned)player->maxHealth); - Execute
draw_text(frame, 12, 58, text, 2, COLOR_WHITE); - Execute
snprintf(text, sizeof(text), "Attack: %u", (unsigned)player->attack); - Execute
draw_text(frame, 12, 81, text, 2, COLOR_WHITE); - Execute
snprintf(text, sizeof(text), "Defence: %u", (unsigned)player->defence); - Execute
draw_text(frame, 12, 104, text, 2, COLOR_WHITE); - Execute
snprintf(text, sizeof(text), "Level choices: %u", (unsigned)player->unspentStatPoints); - Execute
draw_text(frame, 12, 127, text, 2, COLOR_WHITE);
False- Decision
status_page == 1UTrue- Execute
snprintf(text, sizeof(text), "Gold: %lu", (unsigned long)player->gold); - Execute
draw_text(frame, 12, 40, text, 2, COLOR_WHITE); - Execute
snprintf(text, sizeof(text), "Keys: %u", (unsigned)player->keys); - Execute
draw_text(frame, 12, 67, text, 2, COLOR_WHITE); - Execute
snprintf(text, sizeof(text), "Health potions: %u", (unsigned)player->healthPotions); - Execute
draw_text(frame, 12, 94, text, 2, COLOR_WHITE); - Execute
snprintf(text, sizeof(text), "Special items: %u", specialItemCount(player->inventoryFlags)); - Execute
draw_text(frame, 12, 121, text, 2, COLOR_WHITE);
False- Execute
snprintf(text, sizeof(text), "MOVES %lu", (unsigned long)player->moves); - Execute
draw_text_centred(frame, 160, 26, text, 2, COLOR_CYAN); - Execute
formatPlayedTime(text, sizeof(text), player->timePlayedSeconds); - Execute
draw_text_centred(frame, 160, 47, text, 2, COLOR_WHITE); - FOR loop
unsigned floor = 0; floor < DUNGEON_FLOOR_COUNT; ++floorRepeat body while the loop condition permits; then continue below.
- Execute
unsigned total_rooms = 0U; - Execute
const unsigned explored_rooms = exploredRoomCount(floor, &total_rooms); - Execute
snprintf(text, sizeof(text), "Floor %u: %u/%u rooms", floor + 1U, explored_rooms, total_rooms); - Execute
draw_text_centred(frame, 160, 66 + (int)floor * 22, text, 2, COLOR_WHITE);
- Execute
draw_text_centred(frame, 160, 153, status_page == 0U && player->unspentStatPoints > 0U ? "A/K NEXT C BONUS E CLOSE" : "A/K NEXT E CLOSE BOOT BACK", 1, COLOR_LIGHTGRAY);
Read original function source
static void drawStatusScreen(uint16_t *frame)
{
const DungeonPlayerState *player = getDungeonPlayerState();
char text[40];
clear(frame, COLOR_BLACK);
draw_text_centred(frame, 160, 5, status_page == 0U ? "STATUS" : status_page == 1U ? "INVENTORY" : "PROGRESS", 2, COLOR_CYAN);
snprintf(text, sizeof(text), "%u/3", status_page + 1U);
draw_text(frame, 292, 7, text, 1, COLOR_DARKGRAY);
if (status_page == 0U) {
snprintf(text, sizeof(text), "Lvl %u XP %lu/%lu", (unsigned)player->level, (unsigned long)player->experience, (unsigned long)dungeonExperienceNeeded(player->level)); draw_text(frame, 12, 35, text, 2, COLOR_WHITE);
snprintf(text, sizeof(text), "Health: %u/%u", (unsigned)player->health, (unsigned)player->maxHealth); draw_text(frame, 12, 58, text, 2, COLOR_WHITE);
snprintf(text, sizeof(text), "Attack: %u", (unsigned)player->attack); draw_text(frame, 12, 81, text, 2, COLOR_WHITE);
snprintf(text, sizeof(text), "Defence: %u", (unsigned)player->defence); draw_text(frame, 12, 104, text, 2, COLOR_WHITE);
snprintf(text, sizeof(text), "Level choices: %u", (unsigned)player->unspentStatPoints); draw_text(frame, 12, 127, text, 2, COLOR_WHITE);
} else if (status_page == 1U) {
snprintf(text, sizeof(text), "Gold: %lu", (unsigned long)player->gold); draw_text(frame, 12, 40, text, 2, COLOR_WHITE);
snprintf(text, sizeof(text), "Keys: %u", (unsigned)player->keys); draw_text(frame, 12, 67, text, 2, COLOR_WHITE);
snprintf(text, sizeof(text), "Health potions: %u", (unsigned)player->healthPotions); draw_text(frame, 12, 94, text, 2, COLOR_WHITE);
snprintf(text, sizeof(text), "Special items: %u", specialItemCount(player->inventoryFlags)); draw_text(frame, 12, 121, text, 2, COLOR_WHITE);
} else {
snprintf(text, sizeof(text), "MOVES %lu", (unsigned long)player->moves);
draw_text_centred(frame, 160, 26, text, 2, COLOR_CYAN);
formatPlayedTime(text, sizeof(text), player->timePlayedSeconds);
draw_text_centred(frame, 160, 47, text, 2, COLOR_WHITE);
for (unsigned floor = 0; floor < DUNGEON_FLOOR_COUNT; ++floor) {
unsigned total_rooms = 0U;
const unsigned explored_rooms = exploredRoomCount(floor, &total_rooms);
snprintf(text, sizeof(text), "Floor %u: %u/%u rooms", floor + 1U, explored_rooms, total_rooms);
draw_text_centred(frame, 160, 66 + (int)floor * 22, text, 2, COLOR_WHITE);
}
}
draw_text_centred(frame, 160, 153, status_page == 0U && player->unspentStatPoints > 0U ? "A/K NEXT C BONUS E CLOSE" : "A/K NEXT E CLOSE BOOT BACK", 1, COLOR_LIGHTGRAY);
}drawRewardScreen() · source line 1453
static void drawRewardScreen(uint16_t *frame)
Calls into this documentation:
- Decision
!image_visibleTrue- Execute
clear(frame, COLOR_BLACK);
FalseContinue after this decision.
- Execute
drawPet2001TextCentredTransparent(frame, 160, 31, reward_is_key ? "KEY" : "TREASURE", reward_is_key ? COLOR_CYAN : COLOR_YELLOW, 3); - Execute
drawPet2001TextCentredTransparent(frame, 160, 80, reward_text, COLOR_WHITE, 2); - Execute
drawPet2001TextCentredTransparent(frame, 160, 145, "A CONTINUE", COLOR_LIGHTGRAY, 1);
Read original function source
static void drawRewardScreen(uint16_t *frame)
{
if (!image_visible) clear(frame, COLOR_BLACK);
drawPet2001TextCentredTransparent(frame, 160, 31, reward_is_key ? "KEY" : "TREASURE",
reward_is_key ? COLOR_CYAN : COLOR_YELLOW, 3);
drawPet2001TextCentredTransparent(frame, 160, 80, reward_text, COLOR_WHITE, 2);
drawPet2001TextCentredTransparent(frame, 160, 145, "A CONTINUE", COLOR_LIGHTGRAY, 1);
}drawTrapResultScreen() · source line 1462
static void drawTrapResultScreen(uint16_t *frame)
Calls into this documentation:
- Execute
char text[40]; - Decision
!image_visibleTrue- Execute
clear(frame, COLOR_BLACK);
FalseContinue after this decision.
- Execute
drawPet2001TextCentredTransparent(frame, 160, 8, trap_defeated ? "DEFEATED" : "TRAP!", trap_defeated ? COLOR_RED : COLOR_YELLOW, 2); - Decision
trap_defeatedTrue- Execute
snprintf(text, sizeof(text), "RETURNED WITH HALF HEALTH");
False- Execute
snprintf(text, sizeof(text), "DAMAGE %u", (unsigned)trap_damage);
- Execute
drawPet2001TextCentredTransparent(frame, 160, 137, text, COLOR_YELLOW, 1); - Execute
drawPet2001TextCentredTransparent(frame, 160, 153, "A CONTINUE", COLOR_WHITE, 1);
Read original function source
static void drawTrapResultScreen(uint16_t *frame)
{
char text[40];
if (!image_visible) clear(frame, COLOR_BLACK);
drawPet2001TextCentredTransparent(frame, 160, 8, trap_defeated ? "DEFEATED" : "TRAP!",
trap_defeated ? COLOR_RED : COLOR_YELLOW, 2);
if (trap_defeated) snprintf(text, sizeof(text), "RETURNED WITH HALF HEALTH");
else snprintf(text, sizeof(text), "DAMAGE %u", (unsigned)trap_damage);
drawPet2001TextCentredTransparent(frame, 160, 137, text, COLOR_YELLOW, 1);
drawPet2001TextCentredTransparent(frame, 160, 153, "A CONTINUE", COLOR_WHITE, 1);
}drawLockedDoorScreen() · source line 1474
static void drawLockedDoorScreen(uint16_t *frame)
Calls into this documentation:
- Execute
clear(frame, COLOR_BLACK); - Execute
drawPet2001TextCentredTransparent(frame, 160, 37, "LOCKED DOOR", COLOR_RED, 3); - Execute
drawPet2001TextCentredTransparent(frame, 160, 85, "FIND THE KEY", COLOR_YELLOW, 2); - Execute
drawPet2001TextCentredTransparent(frame, 160, 145, "A RETURN", COLOR_LIGHTGRAY, 1);
Read original function source
static void drawLockedDoorScreen(uint16_t *frame)
{
clear(frame, COLOR_BLACK);
drawPet2001TextCentredTransparent(frame, 160, 37, "LOCKED DOOR", COLOR_RED, 3);
drawPet2001TextCentredTransparent(frame, 160, 85, "FIND THE KEY", COLOR_YELLOW, 2);
drawPet2001TextCentredTransparent(frame, 160, 145, "A RETURN", COLOR_LIGHTGRAY, 1);
}drawLevelBonusScreen() · source line 1482
static void drawLevelBonusScreen(uint16_t *frame)
Calls into this documentation:
- Execute
static const char *options[] = { "HEALTH", "ATTACK", "DEFENCE" }; - Execute
const DungeonPlayerState *player = getDungeonPlayerState(); - Execute
char text[40]; - Execute
clear(frame, COLOR_BLACK); - Execute
drawPet2001TextCentredTransparent(frame, 160, 16, "LEVEL BONUS", COLOR_CYAN, 2); - Execute
snprintf(text, sizeof(text), "CHOICES %u", (unsigned)player->unspentStatPoints); - Execute
drawPet2001TextCentredTransparent(frame, 160, 47, text, COLOR_WHITE, 1); - FOR loop
unsigned index = 0; index < 3U; ++indexRepeat body while the loop condition permits; then continue below.
- Execute
drawPet2001TextCentredTransparent(frame, 160, 70 + (int)index * 18, options[index], index == selected_stat_option ? COLOR_YELLOW : COLOR_WHITE, 1);
- Execute
drawPet2001TextCentredTransparent(frame, 160, 153, "UP/DOWN SELECT A CHOOSE E BACK", COLOR_LIGHTGRAY, 1);
Read original function source
static void drawLevelBonusScreen(uint16_t *frame)
{
static const char *options[] = { "HEALTH", "ATTACK", "DEFENCE" };
const DungeonPlayerState *player = getDungeonPlayerState();
char text[40];
clear(frame, COLOR_BLACK);
drawPet2001TextCentredTransparent(frame, 160, 16, "LEVEL BONUS", COLOR_CYAN, 2);
snprintf(text, sizeof(text), "CHOICES %u", (unsigned)player->unspentStatPoints);
drawPet2001TextCentredTransparent(frame, 160, 47, text, COLOR_WHITE, 1);
for (unsigned index = 0; index < 3U; ++index)
drawPet2001TextCentredTransparent(frame, 160, 70 + (int)index * 18, options[index],
index == selected_stat_option ? COLOR_YELLOW : COLOR_WHITE, 1);
drawPet2001TextCentredTransparent(frame, 160, 153, "UP/DOWN SELECT A CHOOSE E BACK",
COLOR_LIGHTGRAY, 1);
}drawShopScreen() · source line 1498
static void drawShopScreen(uint16_t *frame)
Calls into this documentation:
- Execute
static const char *labels[] = { "RESTORE HP", "MAX HP +", "ATTACK +", "DEFENCE +", "LEAVE SHOP" }; - Execute
char text[40]; - Execute
clear(frame, COLOR_BLACK); - Execute
drawPet2001TextCentredTransparent(frame, 160, 8, "SHOP", COLOR_CYAN, 2); - Execute
snprintf(text, sizeof(text), "GOLD %lu", (unsigned long)getDungeonPlayerState()->gold); - Execute
drawPet2001TextCentredTransparent(frame, 160, 29, text, COLOR_YELLOW, 1); - FOR loop
unsigned index = 0; index < 5U; ++indexRepeat body while the loop condition permits; then continue below.
- Decision
index < 4UTrue- Execute
snprintf(text, sizeof(text), "%s %lu", labels[index], (unsigned long)shopChoiceCost(index));
False- Execute
snprintf(text, sizeof(text), "%s", labels[index]);
- Execute
drawPet2001TextCentredTransparent(frame, 160, 48 + (int)index * 17, text, index == selected_shop_option ? COLOR_YELLOW : COLOR_WHITE, 1);
- Decision
shop_text[0] != '\0'True- Execute
drawPet2001TextCentredTransparent(frame, 160, 137, shop_text, COLOR_CYAN, 1);
FalseContinue after this decision.
- Execute
drawPet2001TextCentredTransparent(frame, 160, 153, "UP/DOWN SELECT A BUY E EXIT", COLOR_LIGHTGRAY, 1);
Read original function source
static void drawShopScreen(uint16_t *frame)
{
static const char *labels[] = { "RESTORE HP", "MAX HP +", "ATTACK +", "DEFENCE +", "LEAVE SHOP" };
char text[40];
clear(frame, COLOR_BLACK);
drawPet2001TextCentredTransparent(frame, 160, 8, "SHOP", COLOR_CYAN, 2);
snprintf(text, sizeof(text), "GOLD %lu", (unsigned long)getDungeonPlayerState()->gold);
drawPet2001TextCentredTransparent(frame, 160, 29, text, COLOR_YELLOW, 1);
for (unsigned index = 0; index < 5U; ++index) {
if (index < 4U) snprintf(text, sizeof(text), "%s %lu", labels[index], (unsigned long)shopChoiceCost(index));
else snprintf(text, sizeof(text), "%s", labels[index]);
drawPet2001TextCentredTransparent(frame, 160, 48 + (int)index * 17, text,
index == selected_shop_option ? COLOR_YELLOW : COLOR_WHITE, 1);
}
if (shop_text[0] != '\0') {
drawPet2001TextCentredTransparent(frame, 160, 137, shop_text, COLOR_CYAN, 1);
}
drawPet2001TextCentredTransparent(frame, 160, 153, "UP/DOWN SELECT A BUY E EXIT",
COLOR_LIGHTGRAY, 1);
}drawCombatScreen() · source line 1519
static void drawCombatScreen(uint16_t *frame)
Calls into this documentation:
- Execute
static const char *options[] = { "ATTACK", "RUN", "POTION" }; - Execute
const DungeonPlayerState *player = getDungeonPlayerState(); - Execute
char text[40]; - Decision
!image_visibleTrue- Execute
clear(frame, COLOR_BLACK);
FalseContinue after this decision.
- Execute
draw_text_centred(frame, 160, 5, combatEnemyName(combat_state.enemyType), 2, COLOR_RED); - Execute
draw_text(frame, 7, 34, "YOU", 1, COLOR_CYAN); - Execute
snprintf(text, sizeof(text), "HP %u/%u", (unsigned)player->health, (unsigned)player->maxHealth); - Execute
draw_text(frame, 7, 49, text, 1, COLOR_WHITE); - Execute
snprintf(text, sizeof(text), "ATT %u", (unsigned)player->attack); - Execute
draw_text(frame, 7, 63, text, 1, COLOR_WHITE); - Execute
snprintf(text, sizeof(text), "DEF %u", (unsigned)player->defence); - Execute
draw_text(frame, 7, 77, text, 1, COLOR_WHITE); - Execute
draw_text(frame, 238, 34, "ENEMY", 1, COLOR_RED); - Execute
snprintf(text, sizeof(text), "HP %u/%u", (unsigned)combat_state.health, (unsigned)combat_state.maxHealth); - Execute
draw_text(frame, 238, 49, text, 1, COLOR_WHITE); - Execute
snprintf(text, sizeof(text), "ATT %u", (unsigned)combat_state.attack); - Execute
draw_text(frame, 238, 63, text, 1, COLOR_WHITE); - Execute
snprintf(text, sizeof(text), "DEF %u", (unsigned)combat_state.defence); - Execute
draw_text(frame, 238, 77, text, 1, COLOR_WHITE); - Decision
combat_text[0] != '\0'True- Execute
draw_text_centred(frame, 160, 96, combat_text, 1, COLOR_YELLOW);
FalseContinue after this decision.
- FOR loop
unsigned index = 0; index < combatOptionCount(); ++indexRepeat body while the loop condition permits; then continue below.
- Execute
draw_text_centred(frame, 160, 110 + (int)index * 14, options[index], 1, index == selected_combat_option ? COLOR_YELLOW : COLOR_WHITE);
- Execute
draw_text_centred(frame, 160, 156, "UP/DOWN SELECT A CHOOSE", 1, COLOR_LIGHTGRAY);
Read original function source
static void drawCombatScreen(uint16_t *frame)
{
static const char *options[] = { "ATTACK", "RUN", "POTION" };
const DungeonPlayerState *player = getDungeonPlayerState();
char text[40];
if (!image_visible) clear(frame, COLOR_BLACK);
draw_text_centred(frame, 160, 5, combatEnemyName(combat_state.enemyType), 2, COLOR_RED);
draw_text(frame, 7, 34, "YOU", 1, COLOR_CYAN);
snprintf(text, sizeof(text), "HP %u/%u", (unsigned)player->health, (unsigned)player->maxHealth); draw_text(frame, 7, 49, text, 1, COLOR_WHITE);
snprintf(text, sizeof(text), "ATT %u", (unsigned)player->attack); draw_text(frame, 7, 63, text, 1, COLOR_WHITE);
snprintf(text, sizeof(text), "DEF %u", (unsigned)player->defence); draw_text(frame, 7, 77, text, 1, COLOR_WHITE);
draw_text(frame, 238, 34, "ENEMY", 1, COLOR_RED);
snprintf(text, sizeof(text), "HP %u/%u", (unsigned)combat_state.health, (unsigned)combat_state.maxHealth); draw_text(frame, 238, 49, text, 1, COLOR_WHITE);
snprintf(text, sizeof(text), "ATT %u", (unsigned)combat_state.attack); draw_text(frame, 238, 63, text, 1, COLOR_WHITE);
snprintf(text, sizeof(text), "DEF %u", (unsigned)combat_state.defence); draw_text(frame, 238, 77, text, 1, COLOR_WHITE);
if (combat_text[0] != '\0') draw_text_centred(frame, 160, 96, combat_text, 1, COLOR_YELLOW);
for (unsigned index = 0; index < combatOptionCount(); ++index)
draw_text_centred(frame, 160, 110 + (int)index * 14, options[index], 1, index == selected_combat_option ? COLOR_YELLOW : COLOR_WHITE);
draw_text_centred(frame, 160, 156, "UP/DOWN SELECT A CHOOSE", 1, COLOR_LIGHTGRAY);
}drawCombatResultScreen() · source line 1540
static void drawCombatResultScreen(uint16_t *frame)
Calls into this documentation:
- Execute
clear(frame, COLOR_BLACK); - Execute
drawPet2001TextCentredTransparent(frame, 160, 37, combat_result_title == NULL ? "COMBAT" : combat_result_title, combat_result_color, 3); - Execute
drawPet2001TextCentredTransparent(frame, 160, 88, combat_text, COLOR_WHITE, 1); - Execute
drawPet2001TextCentredTransparent(frame, 160, 145, "A CONTINUE", COLOR_LIGHTGRAY, 1);
Read original function source
static void drawCombatResultScreen(uint16_t *frame)
{
clear(frame, COLOR_BLACK);
drawPet2001TextCentredTransparent(frame, 160, 37,
combat_result_title == NULL ? "COMBAT" : combat_result_title,
combat_result_color, 3);
drawPet2001TextCentredTransparent(frame, 160, 88, combat_text, COLOR_WHITE, 1);
drawPet2001TextCentredTransparent(frame, 160, 145, "A CONTINUE", COLOR_LIGHTGRAY, 1);
}drawCompleteScreen() · source line 1550
static void drawCompleteScreen(uint16_t *frame)
Calls into this documentation:
- Execute
const DungeonPlayerState *player = getDungeonPlayerState(); - Execute
char text[40]; - Execute
clear(frame, COLOR_BLACK); - Execute
drawPet2001TextCentredTransparent(frame, 160, 18, "YOU WIN", COLOR_GREEN, 3); - Execute
snprintf(text, sizeof(text), "LEVEL %u XP %lu", (unsigned)player->level, (unsigned long)player->experience); - Execute
drawPet2001TextCentredTransparent(frame, 160, 67, text, COLOR_WHITE, 1); - Execute
snprintf(text, sizeof(text), "GOLD %lu MOVES %lu", (unsigned long)player->gold, (unsigned long)player->moves); - Execute
drawPet2001TextCentredTransparent(frame, 160, 89, text, COLOR_YELLOW, 1); - Decision
history_result.error == DUNGEON_HISTORY_OK && completion_rank > 0UTrue- Execute
snprintf(text, sizeof(text), "HALL OF FAME #%u", (unsigned)completion_rank); - Execute
drawPet2001TextCentredTransparent(frame, 160, 112, text, COLOR_CYAN, 1);
FalseContinue after this decision.
- Execute
drawPet2001TextCentredTransparent(frame, 160, 145, "A RETURN TO GAMES", COLOR_LIGHTGRAY, 1);
Read original function source
static void drawCompleteScreen(uint16_t *frame)
{
const DungeonPlayerState *player = getDungeonPlayerState();
char text[40];
clear(frame, COLOR_BLACK);
drawPet2001TextCentredTransparent(frame, 160, 18, "YOU WIN", COLOR_GREEN, 3);
snprintf(text, sizeof(text), "LEVEL %u XP %lu", (unsigned)player->level, (unsigned long)player->experience);
drawPet2001TextCentredTransparent(frame, 160, 67, text, COLOR_WHITE, 1);
snprintf(text, sizeof(text), "GOLD %lu MOVES %lu", (unsigned long)player->gold, (unsigned long)player->moves);
drawPet2001TextCentredTransparent(frame, 160, 89, text, COLOR_YELLOW, 1);
if (history_result.error == DUNGEON_HISTORY_OK && completion_rank > 0U) {
snprintf(text, sizeof(text), "HALL OF FAME #%u", (unsigned)completion_rank);
drawPet2001TextCentredTransparent(frame, 160, 112, text, COLOR_CYAN, 1);
}
drawPet2001TextCentredTransparent(frame, 160, 145, "A RETURN TO GAMES", COLOR_LIGHTGRAY, 1);
}drawHistoryScreen() · source line 1567
static void drawHistoryScreen(uint16_t *frame)
Calls into this documentation:
- Execute
char text[40]; - Execute
clear(frame, COLOR_BLACK); - Execute
drawPet2001TextCentredTransparent(frame, 160, 7, "DUNGEON HALL OF FAME", COLOR_CYAN, 2); - Decision
history_result.error != DUNGEON_HISTORY_OKTrue- Execute
const char *error_text = dungeonHistoryErrorText(history_result.error); - Execute
const int error_scale = strlen(error_text) > 20U ? 1 : 2; - Execute
drawPet2001TextCentredTransparent(frame, 160, 76, error_text, COLOR_YELLOW, error_scale);
False- Decision
dungeonHistoryCount() == 0UTrue- Execute
drawPet2001TextCentredTransparent(frame, 160, 76, "NO COMPLETIONS YET", COLOR_YELLOW, 2);
False- FOR loop
unsigned index = 0; index < dungeonHistoryCount(); ++indexRepeat body while the loop condition permits; then continue below.
- Execute
const DungeonHistoryRecord *record = dungeonHistoryRecord((uint8_t)index); - Execute
snprintf(text, sizeof(text), "%u %luG L%u %luM", index + 1U, (unsigned long)record->gold, (unsigned)record->level, (unsigned long)record->moves); - Execute
drawPet2001TextCentredTransparent(frame, 160, 32 + (int)index * 21, text, index == 0U ? COLOR_YELLOW : COLOR_WHITE, 1);
- Execute
drawPet2001TextCentredTransparent(frame, 160, 153, "A RETURN TO MENU BOOT BACK", COLOR_LIGHTGRAY, 1);
Read original function source
static void drawHistoryScreen(uint16_t *frame)
{
char text[40];
clear(frame, COLOR_BLACK);
drawPet2001TextCentredTransparent(frame, 160, 7, "DUNGEON HALL OF FAME", COLOR_CYAN, 2);
if (history_result.error != DUNGEON_HISTORY_OK) {
const char *error_text = dungeonHistoryErrorText(history_result.error);
// PET at 2x fits 20 cells; retain longer checksum diagnostics in full.
const int error_scale = strlen(error_text) > 20U ? 1 : 2;
drawPet2001TextCentredTransparent(frame, 160, 76, error_text, COLOR_YELLOW, error_scale);
} else if (dungeonHistoryCount() == 0U) {
drawPet2001TextCentredTransparent(frame, 160, 76, "NO COMPLETIONS YET", COLOR_YELLOW, 2);
} else {
for (unsigned index = 0; index < dungeonHistoryCount(); ++index) {
const DungeonHistoryRecord *record = dungeonHistoryRecord((uint8_t)index);
snprintf(text, sizeof(text), "%u %luG L%u %luM", index + 1U,
(unsigned long)record->gold, (unsigned)record->level, (unsigned long)record->moves);
drawPet2001TextCentredTransparent(frame, 160, 32 + (int)index * 21, text,
index == 0U ? COLOR_YELLOW : COLOR_WHITE, 1);
}
}
drawPet2001TextCentredTransparent(frame, 160, 153, "A RETURN TO MENU BOOT BACK",
COLOR_LIGHTGRAY, 1);
}drawDungeon() · source line 1595
void drawDungeon(uint16_t *frame)
Calls into this documentation:
- Decision
!running || frame == NULLTrue- Exit this function
return;
FalseContinue after this decision.
- Decision
state == DUNGEON_COMBATTrue- Execute
drawCombatScreen(frame); - Exit this function
return;
FalseContinue after this decision.
- Decision
state == DUNGEON_COMBAT_RESULTTrue- Execute
drawCombatResultScreen(frame); - Exit this function
return;
FalseContinue after this decision.
- Decision
state == DUNGEON_COMPLETETrue- Execute
drawCompleteScreen(frame); - Exit this function
return;
FalseContinue after this decision.
- Decision
state == DUNGEON_HISTORYTrue- Execute
drawHistoryScreen(frame); - Exit this function
return;
FalseContinue after this decision.
- Decision
state == DUNGEON_TRAP_RESULTTrue- Execute
drawTrapResultScreen(frame); - Exit this function
return;
FalseContinue after this decision.
- Decision
state == DUNGEON_LOCKED_DOORTrue- Execute
drawLockedDoorScreen(frame); - Exit this function
return;
FalseContinue after this decision.
- Decision
state == DUNGEON_LEVEL_BONUSTrue- Execute
drawLevelBonusScreen(frame); - Exit this function
return;
FalseContinue after this decision.
- Decision
state == DUNGEON_SHOPTrue- Execute
drawShopScreen(frame); - Exit this function
return;
FalseContinue after this decision.
- Decision
state == DUNGEON_REWARDTrue- Execute
drawRewardScreen(frame); - Exit this function
return;
FalseContinue after this decision.
- Decision
state == DUNGEON_ROOM && status_visibleTrue- Execute
drawStatusScreen(frame); - Exit this function
return;
FalseContinue after this decision.
- Decision
state == DUNGEON_ROOM && map_visibleTrue- Execute
drawDungeonMap(frame); - Exit this function
return;
FalseContinue after this decision.
- Decision
state == DUNGEON_ROOM && decode_pendingTrue- Exit this function
return;
FalseContinue after this decision.
- Decision
state == DUNGEON_ROOM && image_visibleTrue- Execute
const DungeonCell *cell = &getDungeonWorld()->floors[player_position.floor].cells[player_position.y][player_position.x]; - Decision
(cell->exits & DUNGEON_EXIT_NORTH) != 0UTrue- Execute
drawExitIcon(frame, DUNGEON_EXIT_NORTH, exitDestinationExplored(DUNGEON_EXIT_NORTH) ? COLOR_GREEN : COLOR_CYAN);
FalseContinue after this decision.
- Decision
(cell->exits & DUNGEON_EXIT_EAST) != 0UTrue- Execute
drawExitIcon(frame, DUNGEON_EXIT_EAST, exitDestinationExplored(DUNGEON_EXIT_EAST) ? COLOR_GREEN : COLOR_CYAN);
FalseContinue after this decision.
- Decision
(cell->exits & DUNGEON_EXIT_SOUTH) != 0UTrue- Execute
drawExitIcon(frame, DUNGEON_EXIT_SOUTH, exitDestinationExplored(DUNGEON_EXIT_SOUTH) ? COLOR_GREEN : COLOR_CYAN);
FalseContinue after this decision.
- Decision
(cell->exits & DUNGEON_EXIT_WEST) != 0UTrue- Execute
drawExitIcon(frame, DUNGEON_EXIT_WEST, exitDestinationExplored(DUNGEON_EXIT_WEST) ? COLOR_GREEN : COLOR_CYAN);
FalseContinue after this decision.
- Decision
stairAvailable(true)True- Execute
drawStairIcon(frame, true);
FalseContinue after this decision.
- Decision
stairAvailable(false)True- Execute
drawStairIcon(frame, false);
FalseContinue after this decision.
- Decision
isShopRoom(cell)True- Execute
drawShopIndicator(frame);
FalseContinue after this decision.
- Decision
ai_modeTrue- Execute
char ai_text[28]; - Execute
snprintf(ai_text, sizeof(ai_text), "AI %s E SPEED", aiSpeedShortText(ai_speed_index)); - Execute
draw_text_centred(frame, 160, 156, ai_text, 1, COLOR_YELLOW);
FalseContinue after this decision.
- Exit this function
return;
FalseContinue after this decision.
- Decision
state == DUNGEON_START_MENU && decode_pendingTrue- Execute
clear(frame, COLOR_BLACK); - Exit this function
return;
FalseContinue after this decision.
- Decision
state != DUNGEON_START_MENU || !image_visibleTrue- Execute
clear(frame, COLOR_BLACK);
FalseContinue after this decision.
- Execute
drawPet2001TextCentredTransparent(frame, 160, 10, "DUNGEON", COLOR_CYAN, 3); - Decision
state == DUNGEON_CREATINGTrue- Execute
const char *checkpoint = "READY TO PLAY"; - Decision
creation_step == CREATE_DELETETrue- Execute
checkpoint = "CLEARING OLD FILES";
False- Decision
creation_step == CREATE_GENERATETrue- Execute
checkpoint = "MAP / ROOMS / KEYS";
False- Decision
creation_step == CREATE_WORLDTrue- Execute
checkpoint = "SAVING DUNGEON MAP";
False- Decision
creation_step == CREATE_PLAYERTrue- Execute
checkpoint = "PLAYER / LEVEL";
False- Decision
creation_step == CREATE_EXPLOREDTrue- Execute
checkpoint = "EXPLORING MAP";
FalseContinue after this decision.
- Execute
drawPet2001TextCentredTransparent(frame, 160, 43, "CREATING NEW DUNGEON", COLOR_WHITE, 2); - Execute
drawPet2001TextCentredTransparent(frame, 160, 82, checkpoint, COLOR_CYAN, 2); - Execute
drawPet2001TextCentredTransparent(frame, 160, 120, "PLEASE WAIT", COLOR_LIGHTGRAY, 1);
False- Decision
storage_result.error != DUNGEON_STORAGE_OKTrue- Execute
drawPet2001TextCentredTransparent(frame, 160, 62, dungeonStorageErrorText(storage_result.error), COLOR_RED, 2); - Execute
drawPet2001TextCentredTransparent(frame, 160, 88, "/spiffs/dungeon.dat", COLOR_WHITE, 1); - Execute
drawPet2001TextCentredTransparent(frame, 160, 116, creation_trace[0] != '\0' ? creation_trace : "A ERASES + CREATES NEW", COLOR_YELLOW, 1);
False- Decision
state == DUNGEON_AI_STARTTrue- Execute
char countdown[20]; - Execute
drawPet2001TextCentredTransparent(frame, 160, 38, "AI MODE", COLOR_CYAN, 3); - Execute
drawPet2001TextCentredTransparent(frame, 160, 80, "NEW DUNGEON", COLOR_WHITE, 2); - Execute
snprintf(countdown, sizeof(countdown), "STARTING IN %u", (unsigned)ai_start_seconds); - Execute
drawPet2001TextCentredTransparent(frame, 160, 124, countdown, COLOR_LIGHTGRAY, 1);
False- Decision
state == DUNGEON_START_MENUTrue- FOR loop
unsigned index = 0; index < sizeof(start_options) / sizeof(start_options[0]); ++indexRepeat body while the loop condition permits; then continue below.
- Execute
drawPet2001TextCentredTransparent(frame, 160, 43 + (int)index * 21, start_options[index], index == selected_option ? COLOR_CYAN : COLOR_WHITE, index == selected_option ? 2 : 1);
False- Decision
state == DUNGEON_NOTICETrue- Execute
const char *notice_text = player_result.error != DUNGEON_PLAYER_OK ? dungeonPlayerErrorText(player_result.error) : explored_result.error != DUNGEON_EXPLORED_OK ? dungeonExploredErrorText(explored_result.error) : selected_option == 1U ? "NEW DUNGEON NEXT" : selected_option == 2U ? "AI MODE NEXT" : "BOOT RETURNS TO GAMES"; - Execute
const int notice_scale = strlen(notice_text) > 20U ? 1 : 2; - Execute
drawPet2001TextCentredTransparent(frame, 160, 67, notice_text, COLOR_YELLOW, notice_scale); - Execute
drawPet2001TextCentredTransparent(frame, 160, 92, creation_trace, COLOR_WHITE, 1); - Execute
drawPet2001TextCentredTransparent(frame, 160, 116, "A RETURNS TO MENU", COLOR_WHITE, 1);
False- Execute
drawPet2001TextCentredTransparent(frame, 160, 75, "ENTRANCE IMAGE FAILED", COLOR_RED, 1); - Execute
drawPet2001TextCentredTransparent(frame, 160, 104, tile_path, COLOR_WHITE, 1);
- Decision
state != DUNGEON_CREATINGTrue- Execute
drawPet2001TextCentredTransparent(frame, 160, 156, "UP/DOWN SELECT A OPEN BOOT BACK", COLOR_LIGHTGRAY, 1);
FalseContinue after this decision.
Read original function source
void drawDungeon(uint16_t *frame)
{
if (!running || frame == NULL) return;
if (state == DUNGEON_COMBAT) {
drawCombatScreen(frame);
return;
}
if (state == DUNGEON_COMBAT_RESULT) {
drawCombatResultScreen(frame);
return;
}
if (state == DUNGEON_COMPLETE) {
drawCompleteScreen(frame);
return;
}
if (state == DUNGEON_HISTORY) {
drawHistoryScreen(frame);
return;
}
if (state == DUNGEON_TRAP_RESULT) {
drawTrapResultScreen(frame);
return;
}
if (state == DUNGEON_LOCKED_DOOR) {
drawLockedDoorScreen(frame);
return;
}
if (state == DUNGEON_LEVEL_BONUS) {
drawLevelBonusScreen(frame);
return;
}
if (state == DUNGEON_SHOP) {
drawShopScreen(frame);
return;
}
if (state == DUNGEON_REWARD) {
drawRewardScreen(frame);
return;
}
if (state == DUNGEON_ROOM && status_visible) {
drawStatusScreen(frame);
return;
}
if (state == DUNGEON_ROOM && map_visible) {
drawDungeonMap(frame);
return;
}
if (state == DUNGEON_ROOM && decode_pending) return;
if (state == DUNGEON_ROOM && image_visible) {
const DungeonCell *cell = &getDungeonWorld()->floors[player_position.floor].cells[player_position.y][player_position.x];
if ((cell->exits & DUNGEON_EXIT_NORTH) != 0U) drawExitIcon(frame, DUNGEON_EXIT_NORTH, exitDestinationExplored(DUNGEON_EXIT_NORTH) ? COLOR_GREEN : COLOR_CYAN);
if ((cell->exits & DUNGEON_EXIT_EAST) != 0U) drawExitIcon(frame, DUNGEON_EXIT_EAST, exitDestinationExplored(DUNGEON_EXIT_EAST) ? COLOR_GREEN : COLOR_CYAN);
if ((cell->exits & DUNGEON_EXIT_SOUTH) != 0U) drawExitIcon(frame, DUNGEON_EXIT_SOUTH, exitDestinationExplored(DUNGEON_EXIT_SOUTH) ? COLOR_GREEN : COLOR_CYAN);
if ((cell->exits & DUNGEON_EXIT_WEST) != 0U) drawExitIcon(frame, DUNGEON_EXIT_WEST, exitDestinationExplored(DUNGEON_EXIT_WEST) ? COLOR_GREEN : COLOR_CYAN);
if (stairAvailable(true)) drawStairIcon(frame, true);
if (stairAvailable(false)) drawStairIcon(frame, false);
if (isShopRoom(cell)) drawShopIndicator(frame);
if (ai_mode) {
char ai_text[28];
snprintf(ai_text, sizeof(ai_text), "AI %s E SPEED", aiSpeedShortText(ai_speed_index));
draw_text_centred(frame, 160, 156, ai_text, 1, COLOR_YELLOW);
}
return;
}
if (state == DUNGEON_START_MENU && decode_pending) {
// Avoid showing the previous screen during the one-frame JPEG decode.
clear(frame, COLOR_BLACK);
return;
}
// PET start-menu text is transparent, so retain the decoded JPEG beneath it.
if (state != DUNGEON_START_MENU || !image_visible) clear(frame, COLOR_BLACK);
drawPet2001TextCentredTransparent(frame, 160, 10, "DUNGEON", COLOR_CYAN, 3);
if (state == DUNGEON_CREATING) {
const char *checkpoint = "READY TO PLAY";
if (creation_step == CREATE_DELETE) checkpoint = "CLEARING OLD FILES";
else if (creation_step == CREATE_GENERATE) checkpoint = "MAP / ROOMS / KEYS";
else if (creation_step == CREATE_WORLD) checkpoint = "SAVING DUNGEON MAP";
else if (creation_step == CREATE_PLAYER) checkpoint = "PLAYER / LEVEL";
else if (creation_step == CREATE_EXPLORED) checkpoint = "EXPLORING MAP";
drawPet2001TextCentredTransparent(frame, 160, 43, "CREATING NEW DUNGEON", COLOR_WHITE, 2);
drawPet2001TextCentredTransparent(frame, 160, 82, checkpoint, COLOR_CYAN, 2);
drawPet2001TextCentredTransparent(frame, 160, 120, "PLEASE WAIT", COLOR_LIGHTGRAY, 1);
} else if (storage_result.error != DUNGEON_STORAGE_OK) {
drawPet2001TextCentredTransparent(frame, 160, 62, dungeonStorageErrorText(storage_result.error), COLOR_RED, 2);
drawPet2001TextCentredTransparent(frame, 160, 88, "/spiffs/dungeon.dat", COLOR_WHITE, 1);
drawPet2001TextCentredTransparent(frame, 160, 116, creation_trace[0] != '\0' ? creation_trace : "A ERASES + CREATES NEW", COLOR_YELLOW, 1);
} else if (state == DUNGEON_AI_START) {
char countdown[20];
drawPet2001TextCentredTransparent(frame, 160, 38, "AI MODE", COLOR_CYAN, 3);
drawPet2001TextCentredTransparent(frame, 160, 80, "NEW DUNGEON", COLOR_WHITE, 2);
snprintf(countdown, sizeof(countdown), "STARTING IN %u", (unsigned)ai_start_seconds);
drawPet2001TextCentredTransparent(frame, 160, 124, countdown, COLOR_LIGHTGRAY, 1);
} else if (state == DUNGEON_START_MENU) {
for (unsigned index = 0; index < sizeof(start_options) / sizeof(start_options[0]); ++index) {
drawPet2001TextCentredTransparent(frame, 160, 43 + (int)index * 21, start_options[index],
index == selected_option ? COLOR_CYAN : COLOR_WHITE,
index == selected_option ? 2 : 1);
}
} else if (state == DUNGEON_NOTICE) {
const char *notice_text = player_result.error != DUNGEON_PLAYER_OK ? dungeonPlayerErrorText(player_result.error) : explored_result.error != DUNGEON_EXPLORED_OK ? dungeonExploredErrorText(explored_result.error) : selected_option == 1U ? "NEW DUNGEON NEXT" : selected_option == 2U ? "AI MODE NEXT" : "BOOT RETURNS TO GAMES";
// PET at 2x fits 20 cells. Keep longer storage/player diagnostics visible.
const int notice_scale = strlen(notice_text) > 20U ? 1 : 2;
drawPet2001TextCentredTransparent(frame, 160, 67, notice_text, COLOR_YELLOW, notice_scale);
drawPet2001TextCentredTransparent(frame, 160, 92, creation_trace, COLOR_WHITE, 1);
drawPet2001TextCentredTransparent(frame, 160, 116, "A RETURNS TO MENU", COLOR_WHITE, 1);
} else {
drawPet2001TextCentredTransparent(frame, 160, 75, "ENTRANCE IMAGE FAILED", COLOR_RED, 1);
drawPet2001TextCentredTransparent(frame, 160, 104, tile_path, COLOR_WHITE, 1);
}
if (state != DUNGEON_CREATING) {
drawPet2001TextCentredTransparent(frame, 160, 156, "UP/DOWN SELECT A OPEN BOOT BACK",
COLOR_LIGHTGRAY, 1);
}
}Complete original file, including constants and data
/* Coded By JD(2026) */
#error "Reference listing only — not provided as build-ready source."
#include "dungeon.h"
#include "dungeon_combat.h"
#include "dungeon_config.h"
#include "dungeon_storage.h"
#include "dungeon_tiles.h"
#include "dungeon_player.h"
#include "dungeon_explored.h"
#include "dungeon_history.h"
#include "dungeon_generator.h"
#include "ai_speed.h"
#include "game_graphics.h"
#include "PET2001_font.h"
#include "image_decoder.h"
#include "menu.h"
#include "esp_random.h"
#include "esp_log.h"
#include "esp_system.h"
#include <stdio.h>
#include <string.h>
#define JOYSTICK_ENTER 600
#define JOYSTICK_RELEASE 350
#define REPEAT_START_MS 400U
#define REPEAT_MS 150U
#define DUNGEON_CREATION_STAGE_PRESENT_MS 250U
#define DUNGEON_CREATION_READY_MS 500U
#define DUNGEON_TILE_ROOT "/spiffs/dungeon/dungeon_tiles"
#define DUNGEON_START_BACKGROUND_PATH DUNGEON_TILE_ROOT "/dungeon_back.jpg"
#define DUNGEON_RUN_KEY_REVEALED 0x00000001U
#define DUNGEON_RUN_COMPLETED 0x00000002U
/* -------------------------------------------------------------------------- */
/* Dungeon session state */
/* -------------------------------------------------------------------------- */
typedef enum { DUNGEON_START_MENU, DUNGEON_AI_START, DUNGEON_CREATING, DUNGEON_ROOM, DUNGEON_NOTICE, DUNGEON_REWARD, DUNGEON_TRAP_RESULT, DUNGEON_LOCKED_DOOR, DUNGEON_LEVEL_BONUS, DUNGEON_SHOP, DUNGEON_COMBAT, DUNGEON_COMBAT_RESULT, DUNGEON_COMPLETE, DUNGEON_HISTORY } dungeon_state_t;
typedef enum { COMBAT_ATTACK, COMBAT_RUN, COMBAT_POTION } combat_option_t;
typedef enum { CREATE_DELETE, CREATE_GENERATE, CREATE_WORLD, CREATE_PLAYER, CREATE_EXPLORED, CREATE_READY } creation_step_t;
static const char *start_options[] = { "CONTINUE", "NEW", "AI", "HIGH SCORES", "QUIT" };
static DungeonStorageResult storage_result;
static DungeonDeleteResult delete_result;
static DungeonPlayerResult player_result;
static DungeonExploredResult explored_result;
static DungeonHistoryResult history_result;
static dungeon_state_t state;
static char tile_path[112];
static char reward_text[40];
static char combat_text[40];
static char shop_text[40];
static char creation_trace[64];
static uint16_t trap_damage;
static uint16_t combat_levels_gained;
static bool reward_is_key;
static bool combat_victory;
static bool trap_defeated;
static const char *combat_result_title;
static uint16_t combat_result_color;
static uint8_t completion_rank;
static DungeonCombatState combat_state;
static DungeonPosition encounter_source;
static uint32_t last_move;
static unsigned selected_option, selected_combat_option, selected_stat_option, selected_shop_option;
static unsigned map_floor, map_page, status_page;
static DungeonPosition player_position;
static int held_direction;
static bool running, decode_pending, image_visible, waiting_for_centre, repeating, map_visible, status_visible;
static bool play_timer_running;
static uint32_t last_play_time_ms;
static bool ai_mode;
static uint32_t ai_start_at;
static uint8_t ai_start_seconds;
static uint32_t ai_last_move;
static bool ai_reverse_stair_blocked;
static bool ai_blocked_stair_up;
static bool ai_shop_visited;
static unsigned ai_last_shop_option;
static uint8_t ai_speed_index;
static uint16_t ai_route_queue[DUNGEON_FLOOR_COUNT * DUNGEON_WIDTH * DUNGEON_HEIGHT];
static uint8_t ai_route_first_option[DUNGEON_FLOOR_COUNT * DUNGEON_WIDTH * DUNGEON_HEIGHT];
static DungeonWorldGenerationStats creation_generation_stats;
static creation_step_t creation_step;
static uint32_t creation_step_at;
static uint32_t creation_ready_at;
static void traceDungeonCreation(const char *stage, int error, int errno_value)
{
/* Temporary creation diagnostics retained for future SPIFFS investigation.
snprintf(creation_trace, sizeof(creation_trace), "%s E%d N%d", stage, error, errno_value);
ESP_LOGI("dungeon", "CREATE %s error=%d errno=%d", stage, error, errno_value);
*/
(void)stage;
(void)error;
(void)errno_value;
}
static void startPlayTimer(uint32_t now_ms)
{
last_play_time_ms = now_ms;
play_timer_running = true;
}
static void commitPlayTime(uint32_t now_ms)
{
if (!play_timer_running) return;
const uint32_t elapsed = now_ms - last_play_time_ms;
const uint32_t elapsed_seconds = elapsed / 1000U;
if (elapsed_seconds == 0U) return;
DungeonPlayerState *player = getDungeonPlayerState();
player->timePlayedSeconds = UINT32_MAX - player->timePlayedSeconds < elapsed_seconds
? UINT32_MAX : player->timePlayedSeconds + elapsed_seconds;
last_play_time_ms += elapsed_seconds * 1000U;
}
/* -------------------------------------------------------------------------- */
/* Room image and map-exploration helpers */
/* -------------------------------------------------------------------------- */
static bool prepareEntranceImage(void)
{
DungeonWorld *world = getDungeonWorld();
const DungeonCell *cell = &world->floors[world->entrance.floor].cells[world->entrance.y][world->entrance.x];
return resolveDungeonTilePath(cell, tile_path, sizeof(tile_path));
}
static void prepareStartBackground(void)
{
const int written = snprintf(tile_path, sizeof(tile_path), "%s", DUNGEON_START_BACKGROUND_PATH);
image_visible = false;
decode_pending = written > 0 && (size_t)written < sizeof(tile_path);
}
static bool prepareRoomImage(const DungeonPosition *position)
{
const DungeonCell *cell = &getDungeonWorld()->floors[position->floor].cells[position->y][position->x];
return resolveDungeonTilePath(cell, tile_path, sizeof(tile_path));
}
static void recordExplored(const DungeonPosition *position)
{
markDungeonExplored(position, &explored_result);
}
static bool isEncounterRoom(const DungeonCell *cell)
{
return cell->tileType == DUNGEON_TILE_ROOM_OPEN || cell->tileType == DUNGEON_TILE_DEAD_END_ROOM_ROUND ||
cell->tileType == DUNGEON_TILE_DEAD_END_ROOM_SQUARE || cell->tileType == DUNGEON_TILE_DEAD_END_ROOM_ROUND_ALT ||
cell->tileType == DUNGEON_TILE_EXIT;
}
static bool isShopRoom(const DungeonCell *cell)
{
return cell->tileType == DUNGEON_TILE_STAIRS_UP || cell->tileType == DUNGEON_TILE_STAIRS_DOWN || cell->tileType == DUNGEON_TILE_STAIRS_BOTH;
}
static void applyMoveRecovery(DungeonPlayerState *player)
{
const DungeonConfig *config = getDungeonConfig();
if (config->healthRegenMoves == 0U || player->moves % config->healthRegenMoves != 0U) return;
const uint16_t limit = (uint16_t)(((uint32_t)player->maxHealth * config->healthRegenLimitPercent + 99U) / 100U);
if (player->health < limit) {
const uint32_t recovered = (uint32_t)player->health + config->healthRegenAmount;
player->health = recovered > limit ? limit : (uint16_t)recovered;
}
}
static uint8_t encounterEnemyType(const DungeonCell *cell)
{
const uint8_t saved_type = (uint8_t)((cell->eventFlags & DUNGEON_ENEMY_DETAIL_MASK) >> DUNGEON_ENEMY_DETAIL_SHIFT);
if (saved_type != DUNGEON_ENEMY_NONE) return saved_type;
const DungeonConfig *config = getDungeonConfig();
return player_position.floor == 0U ? config->floor1EnemyMinLevel : player_position.floor == 1U ? config->floor2EnemyMinLevel : config->floor3EnemyMinLevel;
}
static bool collectRoomReward(void);
static void returnToRoom(void);
static void prepareEventImage(const char *prefix, uint8_t variant)
{
const unsigned image_variant = variant == 0U ? 1U : variant;
const int written = snprintf(tile_path, sizeof(tile_path), DUNGEON_TILE_ROOT "/t_m/%s%02u.jpg", prefix, image_variant);
image_visible = false;
decode_pending = written > 0 && (size_t)written < sizeof(tile_path);
}
static void prepareRewardImage(const DungeonCell *cell, DungeonTreasureType treasure)
{
const char *prefix = reward_is_key ? "t_key_" : treasure == DUNGEON_TREASURE_HEALTH ? "t_health_" :
treasure == DUNGEON_TREASURE_LIFE ? "t_life_" : treasure == DUNGEON_TREASURE_ATTACK ? "t_att_" :
treasure == DUNGEON_TREASURE_DEFENCE ? "t_def_" : "t_gold_";
prepareEventImage(prefix, cell->variant);
}
/* -------------------------------------------------------------------------- */
/* Room events: traps, encounters, keys, and treasure */
/* -------------------------------------------------------------------------- */
static bool resolveRoomTrap(void)
{
DungeonCell *cell = &getDungeonWorld()->floors[player_position.floor].cells[player_position.y][player_position.x];
DungeonPlayerState *player = getDungeonPlayerState();
const DungeonPlayerState previous_player = *player;
const uint16_t previous_flags = cell->stateFlags;
const DungeonConfig *config = getDungeonConfig();
uint16_t minimum = player_position.floor == 0U ? config->floor1TrapDamageMin : player_position.floor == 1U ? config->floor2TrapDamageMin : config->floor3TrapDamageMin;
uint16_t maximum = player_position.floor == 0U ? config->floor1TrapDamageMax : player_position.floor == 1U ? config->floor2TrapDamageMax : config->floor3TrapDamageMax;
const uint32_t rolled_damage = minimum + esp_random() % ((uint32_t)maximum - minimum + 1U);
const uint32_t reduced_damage = rolled_damage > player->defence ? rolled_damage - player->defence : config->combatMinimumDamage;
trap_damage = (uint16_t)(reduced_damage < config->combatMinimumDamage ? config->combatMinimumDamage : reduced_damage);
player->health = trap_damage >= player->health ? 0U : (uint16_t)(player->health - trap_damage);
cell->stateFlags |= DUNGEON_STATE_TRIGGERED;
if (player->health == 0U) {
DungeonPosition respawn = getDungeonWorld()->entrance;
if (player_position.floor > 0U) respawn = getDungeonWorld()->stairLinks[player_position.floor - 1U].lower;
player->position = respawn;
player->health = (uint16_t)(player->maxHealth / 2U + player->maxHealth % 2U);
player_position = respawn;
trap_defeated = true;
} else trap_defeated = false;
if (!saveDungeonWorld(&storage_result) || !saveDungeonPlayer(&player_result)) {
*player = previous_player;
player_position = previous_player.position;
cell->stateFlags = previous_flags;
saveDungeonWorld(&storage_result);
return false;
}
recordExplored(&player_position);
state = DUNGEON_TRAP_RESULT;
prepareEventImage("t_trap_", cell->variant);
return true;
}
static bool startRoomEvent(const DungeonPosition *source)
{
const DungeonCell *cell = &getDungeonWorld()->floors[player_position.floor].cells[player_position.y][player_position.x];
if ((cell->eventFlags & DUNGEON_EVENT_TRAP) != 0U && (cell->stateFlags & DUNGEON_STATE_TRIGGERED) == 0U) return resolveRoomTrap();
if ((cell->eventFlags & DUNGEON_EVENT_REQUIRES_KEY) != 0U && (cell->stateFlags & DUNGEON_STATE_UNLOCKED) == 0U && getDungeonPlayerState()->keys == 0U) {
if (encounterEnemyType(cell) == DUNGEON_ENEMY_DRAGON) {
DungeonPlayerState *player = getDungeonPlayerState();
const uint32_t previous_flags = player->runFlags;
player->runFlags |= DUNGEON_RUN_KEY_REVEALED;
if (!saveDungeonPlayer(&player_result)) {
player->runFlags = previous_flags;
state = DUNGEON_NOTICE;
return true;
}
}
encounter_source = *source;
state = DUNGEON_LOCKED_DOOR;
image_visible = false;
decode_pending = false;
return true;
}
if (isEncounterRoom(cell) && (cell->eventFlags & DUNGEON_EVENT_ENCOUNTER) != 0U && (cell->stateFlags & DUNGEON_STATE_COMPLETED) == 0U) {
const uint8_t enemy_type = encounterEnemyType(cell);
if (!startDungeonCombat(enemy_type, &combat_state)) return false;
encounter_source = *source;
selected_combat_option = COMBAT_ATTACK;
combat_text[0] = '\0';
combat_victory = false;
combat_result_title = NULL;
state = DUNGEON_COMBAT;
prepareEventImage(combat_state.enemyType == DUNGEON_ENEMY_LEVEL_1 ? "e_lvl1_" : combat_state.enemyType == DUNGEON_ENEMY_LEVEL_2 ? "e_lvl2_" :
combat_state.enemyType == DUNGEON_ENEMY_LEVEL_3 ? "e_lvl3_" : combat_state.enemyType == DUNGEON_ENEMY_LEVEL_4 ? "e_lvl4_" : "e_drag_", cell->variant);
return true;
}
return collectRoomReward();
}
static bool collectRoomReward(void)
{
DungeonCell *cell = &getDungeonWorld()->floors[player_position.floor].cells[player_position.y][player_position.x];
DungeonPlayerState *player = getDungeonPlayerState();
const DungeonPlayerState previous_player = *player;
const uint16_t previous_flags = cell->stateFlags;
DungeonTreasureType treasure = DUNGEON_TREASURE_GOLD;
bool pending = false;
bool collected = false;
reward_text[0] = '\0';
reward_is_key = false;
if ((cell->eventFlags & DUNGEON_EVENT_KEY) != 0U && (cell->stateFlags & DUNGEON_STATE_KEY_COLLECTED) == 0U) {
if (player->keys < UINT16_MAX) ++player->keys;
cell->stateFlags |= DUNGEON_STATE_KEY_COLLECTED;
snprintf(reward_text, sizeof(reward_text), "KEY COLLECTED");
reward_is_key = true;
pending = true;
collected = true;
} else if ((cell->eventFlags & DUNGEON_EVENT_TREASURE) != 0U && (cell->stateFlags & DUNGEON_STATE_OPENED) == 0U) {
const DungeonConfig *config = getDungeonConfig();
treasure = (DungeonTreasureType)((cell->eventFlags & DUNGEON_TREASURE_DETAIL_MASK) >> DUNGEON_TREASURE_DETAIL_SHIFT);
pending = true;
if (treasure == DUNGEON_TREASURE_HEALTH) {
if (player->health < player->maxHealth) {
player->health = player->maxHealth;
snprintf(reward_text, sizeof(reward_text), "HEALTH RESTORED");
collected = true;
} else if (player->healthPotions < config->maxHealthPotions) {
++player->healthPotions;
snprintf(reward_text, sizeof(reward_text), "POTION +1");
collected = true;
} else snprintf(reward_text, sizeof(reward_text), "CANNOT CARRY");
} else if (treasure == DUNGEON_TREASURE_LIFE) {
player->maxHealth = player->maxHealth <= UINT16_MAX - config->rareLifeHealthGain ? player->maxHealth + config->rareLifeHealthGain : UINT16_MAX;
snprintf(reward_text, sizeof(reward_text), "MAX HEALTH +%u", (unsigned)config->rareLifeHealthGain);
collected = true;
} else if (treasure == DUNGEON_TREASURE_ATTACK) {
player->attack = player->attack <= UINT16_MAX - config->rareAttackGain ? player->attack + config->rareAttackGain : UINT16_MAX;
snprintf(reward_text, sizeof(reward_text), "ATTACK +%u", (unsigned)config->rareAttackGain);
collected = true;
} else if (treasure == DUNGEON_TREASURE_DEFENCE) {
player->defence = player->defence <= UINT16_MAX - config->rareDefenceGain ? player->defence + config->rareDefenceGain : UINT16_MAX;
snprintf(reward_text, sizeof(reward_text), "DEFENCE +%u", (unsigned)config->rareDefenceGain);
collected = true;
} else {
uint16_t minimum = player_position.floor == 0U ? config->floor1GoldMin : player_position.floor == 1U ? config->floor2GoldMin : config->floor3GoldMin;
uint16_t maximum = player_position.floor == 0U ? config->floor1GoldMax : player_position.floor == 1U ? config->floor2GoldMax : config->floor3GoldMax;
const uint32_t amount = minimum + (esp_random() % ((uint32_t)maximum - minimum + 1U));
player->gold = player->gold <= UINT32_MAX - amount ? player->gold + amount : UINT32_MAX;
snprintf(reward_text, sizeof(reward_text), "GOLD +%lu", (unsigned long)amount);
collected = true;
}
if (collected) cell->stateFlags |= DUNGEON_STATE_OPENED;
}
if (!pending) return false;
if (!collected) {
state = DUNGEON_REWARD;
prepareRewardImage(cell, treasure);
return true;
}
if (!saveDungeonWorld(&storage_result) || !saveDungeonPlayer(&player_result)) {
*player = previous_player;
cell->stateFlags = previous_flags;
saveDungeonWorld(&storage_result);
return false;
}
state = DUNGEON_REWARD;
prepareRewardImage(cell, treasure);
return true;
}
static bool movePlayer(uint8_t exit)
{
const DungeonCell *current = &getDungeonWorld()->floors[player_position.floor].cells[player_position.y][player_position.x];
if ((current->exits & exit) == 0U) return false;
DungeonPosition destination = player_position;
if (exit == DUNGEON_EXIT_NORTH && destination.y > 0U) --destination.y;
else if (exit == DUNGEON_EXIT_EAST && destination.x + 1U < DUNGEON_WIDTH) ++destination.x;
else if (exit == DUNGEON_EXIT_SOUTH && destination.y + 1U < DUNGEON_HEIGHT) ++destination.y;
else if (exit == DUNGEON_EXIT_WEST && destination.x > 0U) --destination.x;
else return false;
const DungeonCell *cell = &getDungeonWorld()->floors[destination.floor].cells[destination.y][destination.x];
if (!resolveDungeonTilePath(cell, tile_path, sizeof(tile_path))) return false;
DungeonPlayerState *player = getDungeonPlayerState();
const DungeonPlayerState previous_player = *player;
player->position = destination;
++player->moves;
applyMoveRecovery(player);
if (!saveDungeonPlayer(&player_result)) {
*player = previous_player;
return false;
}
player_position = destination;
recordExplored(&player_position);
if (startRoomEvent(&previous_player.position)) return true;
image_visible = false;
decode_pending = true;
return true;
}
/* -------------------------------------------------------------------------- */
/* Player movement and stair traversal */
/* -------------------------------------------------------------------------- */
static bool moveStairs(bool up)
{
DungeonWorld *world = getDungeonWorld();
const DungeonCell *current = &world->floors[player_position.floor].cells[player_position.y][player_position.x];
if ((up && current->tileType != DUNGEON_TILE_STAIRS_UP && current->tileType != DUNGEON_TILE_STAIRS_BOTH) ||
(!up && current->tileType != DUNGEON_TILE_STAIRS_DOWN && current->tileType != DUNGEON_TILE_STAIRS_BOTH)) return false;
DungeonPosition destination;
bool found = false;
for (unsigned index = 0; index < DUNGEON_FLOOR_COUNT - 1U; ++index) {
const DungeonStairLink *link = &world->stairLinks[index];
if (player_position.floor == link->upper.floor &&
player_position.x == link->upper.x && player_position.y == link->upper.y) {
destination = link->lower;
found = true;
break;
}
if (player_position.floor == link->lower.floor &&
player_position.x == link->lower.x && player_position.y == link->lower.y) {
destination = link->upper;
found = true;
break;
}
}
if (!found) return false;
const DungeonCell *cell = &world->floors[destination.floor].cells[destination.y][destination.x];
if (!resolveDungeonTilePath(cell, tile_path, sizeof(tile_path))) return false;
DungeonPlayerState *player = getDungeonPlayerState();
const DungeonPlayerState previous_player = *player;
player->position = destination;
++player->moves;
applyMoveRecovery(player);
if (!saveDungeonPlayer(&player_result)) {
*player = previous_player;
return false;
}
player_position = destination;
recordExplored(&player_position);
if (startRoomEvent(&previous_player.position)) return true;
image_visible = false;
decode_pending = true;
return true;
}
static bool stairAvailable(bool up)
{
DungeonWorld *world = getDungeonWorld();
const DungeonCell *current = &world->floors[player_position.floor].cells[player_position.y][player_position.x];
if ((up && current->tileType != DUNGEON_TILE_STAIRS_UP && current->tileType != DUNGEON_TILE_STAIRS_BOTH) ||
(!up && current->tileType != DUNGEON_TILE_STAIRS_DOWN && current->tileType != DUNGEON_TILE_STAIRS_BOTH)) return false;
for (unsigned index = 0; index < DUNGEON_FLOOR_COUNT - 1U; ++index) {
const DungeonStairLink *link = &world->stairLinks[index];
if (player_position.floor == link->upper.floor &&
player_position.x == link->upper.x && player_position.y == link->upper.y) return true;
if (player_position.floor == link->lower.floor &&
player_position.x == link->lower.x && player_position.y == link->lower.y) return true;
}
return false;
}
typedef enum { AI_NORTH, AI_EAST, AI_SOUTH, AI_WEST, AI_UP, AI_DOWN } ai_option_t;
static uint16_t aiPositionIndex(DungeonPosition position)
{
return (uint16_t)(((uint16_t)position.floor * DUNGEON_WIDTH * DUNGEON_HEIGHT) +
((uint16_t)position.y * DUNGEON_WIDTH) + position.x);
}
static bool aiSamePosition(DungeonPosition left, DungeonPosition right)
{
return left.floor == right.floor && left.x == right.x && left.y == right.y;
}
static bool aiNeighbour(DungeonPosition position, ai_option_t option, DungeonPosition *destination)
{
DungeonWorld *world = getDungeonWorld();
*destination = position;
if (option == AI_UP || option == AI_DOWN) {
for (uint8_t index = 0U; index < DUNGEON_FLOOR_COUNT - 1U; ++index) {
const DungeonStairLink *link = &world->stairLinks[index];
if (option == AI_DOWN && aiSamePosition(position, link->upper)) { *destination = link->lower; return true; }
if (option == AI_UP && aiSamePosition(position, link->lower)) { *destination = link->upper; return true; }
}
return false;
}
const uint8_t exits[] = { DUNGEON_EXIT_NORTH, DUNGEON_EXIT_EAST, DUNGEON_EXIT_SOUTH, DUNGEON_EXIT_WEST };
const DungeonCell *cell = &world->floors[position.floor].cells[position.y][position.x];
if ((cell->exits & exits[option]) == 0U) return false;
if (option == AI_NORTH && position.y > 0U) --destination->y;
else if (option == AI_EAST && position.x + 1U < DUNGEON_WIDTH) ++destination->x;
else if (option == AI_SOUTH && position.y + 1U < DUNGEON_HEIGHT) ++destination->y;
else if (option == AI_WEST && position.x > 0U) --destination->x;
else return false;
return true;
}
static bool aiFindPath(DungeonPosition target, ai_option_t *first_option)
{
const DungeonPosition start = player_position;
if (aiSamePosition(start, target)) return false;
memset(ai_route_first_option, 0xFF, sizeof(ai_route_first_option));
uint16_t head = 0U, tail = 0U;
const uint16_t start_index = aiPositionIndex(start);
ai_route_queue[tail++] = start_index;
ai_route_first_option[start_index] = 0xFEU;
while (head < tail) {
const uint16_t current_index = ai_route_queue[head++];
DungeonPosition current = { (uint8_t)(current_index / (DUNGEON_WIDTH * DUNGEON_HEIGHT)),
(uint8_t)(current_index % DUNGEON_WIDTH),
(uint8_t)((current_index % (DUNGEON_WIDTH * DUNGEON_HEIGHT)) / DUNGEON_WIDTH) };
for (uint8_t option = AI_NORTH; option <= AI_DOWN; ++option) {
DungeonPosition next;
if (!aiNeighbour(current, (ai_option_t)option, &next)) continue;
const uint16_t next_index = aiPositionIndex(next);
if (ai_route_first_option[next_index] != 0xFFU) continue;
ai_route_first_option[next_index] = current_index == start_index ? option : ai_route_first_option[current_index];
if (aiSamePosition(next, target)) { *first_option = (ai_option_t)ai_route_first_option[next_index]; return true; }
ai_route_queue[tail++] = next_index;
}
}
return false;
}
static bool aiNearestUnexplored(ai_option_t *first_option)
{
const DungeonPosition start = player_position;
memset(ai_route_first_option, 0xFF, sizeof(ai_route_first_option));
uint16_t head = 0U, tail = 0U;
const uint16_t start_index = aiPositionIndex(start);
ai_route_queue[tail++] = start_index;
ai_route_first_option[start_index] = 0xFEU;
while (head < tail) {
const uint16_t current_index = ai_route_queue[head++];
DungeonPosition current = { (uint8_t)(current_index / (DUNGEON_WIDTH * DUNGEON_HEIGHT)),
(uint8_t)(current_index % DUNGEON_WIDTH),
(uint8_t)((current_index % (DUNGEON_WIDTH * DUNGEON_HEIGHT)) / DUNGEON_WIDTH) };
for (uint8_t option = AI_NORTH; option <= AI_DOWN; ++option) {
DungeonPosition next;
if (!aiNeighbour(current, (ai_option_t)option, &next)) continue;
const uint16_t next_index = aiPositionIndex(next);
if (ai_route_first_option[next_index] != 0xFFU) continue;
ai_route_first_option[next_index] = current_index == start_index ? option : ai_route_first_option[current_index];
if (!isDungeonExplored(next.floor, next.x, next.y)) { *first_option = (ai_option_t)ai_route_first_option[next_index]; return true; }
ai_route_queue[tail++] = next_index;
}
}
return false;
}
static bool aiFindUncollectedKey(DungeonPosition *position)
{
DungeonWorld *world = getDungeonWorld();
for (uint8_t floor = 0U; floor < DUNGEON_FLOOR_COUNT; ++floor)
for (uint8_t y = 0U; y < DUNGEON_HEIGHT; ++y)
for (uint8_t x = 0U; x < DUNGEON_WIDTH; ++x) {
const DungeonCell *cell = &world->floors[floor].cells[y][x];
if ((cell->eventFlags & DUNGEON_EVENT_KEY) != 0U &&
(cell->stateFlags & DUNGEON_STATE_KEY_COLLECTED) == 0U) {
*position = (DungeonPosition){floor, x, y};
return true;
}
}
return false;
}
static bool aiChooseMove(ai_option_t *option)
{
DungeonPlayerState *player = getDungeonPlayerState();
DungeonWorld *world = getDungeonWorld();
DungeonPosition target;
if (player->keys == 0U && (player->runFlags & DUNGEON_RUN_KEY_REVEALED) != 0U &&
aiFindUncollectedKey(&target)) return aiFindPath(target, option);
if (player->keys > 0U && player_position.floor < DUNGEON_FLOOR_COUNT - 1U) {
for (uint8_t index = 0U; index < DUNGEON_FLOOR_COUNT - 1U; ++index)
if (world->stairLinks[index].upper.floor == player_position.floor)
return aiSamePosition(player_position, world->stairLinks[index].upper) ? ((*option = AI_DOWN), true) : aiFindPath(world->stairLinks[index].upper, option);
}
return aiNearestUnexplored(option);
}
static void aiMoveOnce(void)
{
ai_option_t option;
if (!aiChooseMove(&option)) return;
if (ai_reverse_stair_blocked && ((option == AI_UP) == ai_blocked_stair_up)) return;
if (option == AI_UP || option == AI_DOWN) {
if (moveStairs(option == AI_UP)) {
ai_reverse_stair_blocked = true;
ai_blocked_stair_up = option == AI_DOWN;
ai_shop_visited = false;
}
return;
}
const uint8_t exits[] = { DUNGEON_EXIT_NORTH, DUNGEON_EXIT_EAST, DUNGEON_EXIT_SOUTH, DUNGEON_EXIT_WEST };
if (movePlayer(exits[option])) {
ai_reverse_stair_blocked = false;
ai_shop_visited = false;
}
}
static void aiChooseCombatAction(void)
{
const DungeonPlayerState *player = getDungeonPlayerState();
if (player->health <= combat_state.health / 2U) selected_combat_option = COMBAT_RUN;
else if (player->healthPotions > 0U && player->health <= player->maxHealth / 2U) selected_combat_option = COMBAT_POTION;
else selected_combat_option = COMBAT_ATTACK;
}
/* -------------------------------------------------------------------------- */
/* Dungeon start, room restoration, and combat results */
/* -------------------------------------------------------------------------- */
void startDungeon(uint32_t now_ms)
{
running = true;
play_timer_running = false;
last_play_time_ms = now_ms;
state = DUNGEON_START_MENU;
selected_option = 0;
image_visible = false;
decode_pending = false;
waiting_for_centre = true;
held_direction = 0;
repeating = false;
map_visible = false;
status_visible = false;
map_floor = 0;
map_page = 0;
status_page = 0;
reward_text[0] = '\0';
combat_text[0] = '\0';
shop_text[0] = '\0';
creation_trace[0] = '\0';
trap_damage = 0U;
combat_levels_gained = 0U;
reward_is_key = false;
combat_victory = false;
ai_mode = false;
ai_start_at = 0U;
ai_start_seconds = 2U;
ai_last_move = 0U;
ai_reverse_stair_blocked = false;
ai_shop_visited = false;
ai_last_shop_option = 4U;
ai_speed_index = AI_SPEED_DEFAULT_INDEX;
trap_defeated = false;
combat_result_title = NULL;
initDungeonConfig();
initDungeonExplored();
initDungeonPlayer();
memset(&player_result, 0, sizeof(player_result));
player_result.error = DUNGEON_PLAYER_OK;
memset(&explored_result, 0, sizeof(explored_result));
explored_result.error = DUNGEON_EXPLORED_OK;
memset(&history_result, 0, sizeof(history_result));
history_result.error = DUNGEON_HISTORY_OK;
completion_rank = 0U;
if (!loadDungeonWorld(&storage_result)) return;
if (!prepareEntranceImage()) {
storage_result.error = DUNGEON_STORAGE_READ_FAILED;
return;
}
prepareStartBackground();
}
void stopDungeon(uint32_t now_ms)
{
commitPlayTime(now_ms);
if (play_timer_running) (void)saveDungeonPlayer(&player_result);
play_timer_running = false;
running = false;
decode_pending = false;
image_visible = false;
}
static unsigned combatOptionCount(void)
{
return getDungeonPlayerState()->healthPotions > 0U ? 3U : 2U;
}
static const char *combatEnemyName(uint8_t enemy_type)
{
switch (enemy_type) {
case DUNGEON_ENEMY_LEVEL_1: return "LEVEL 1";
case DUNGEON_ENEMY_LEVEL_2: return "LEVEL 2";
case DUNGEON_ENEMY_LEVEL_3: return "LEVEL 3";
case DUNGEON_ENEMY_LEVEL_4: return "LEVEL 4";
default: return "DRAGON";
}
}
static void returnToRoom(void)
{
if (!prepareRoomImage(&player_position)) {
player_result.error = DUNGEON_PLAYER_INVALID_POSITION;
state = DUNGEON_NOTICE;
return;
}
state = DUNGEON_ROOM;
image_visible = false;
decode_pending = true;
}
static void beginDungeonCreation(bool delete_old_files, uint32_t now_ms)
{
memset(&creation_generation_stats, 0, sizeof(creation_generation_stats));
creation_step = delete_old_files ? CREATE_DELETE : CREATE_GENERATE;
creation_step_at = now_ms;
state = DUNGEON_CREATING;
image_visible = false;
decode_pending = false;
}
static void advanceDungeonCreation(uint32_t now_ms)
{
if (now_ms - creation_step_at < DUNGEON_CREATION_STAGE_PRESENT_MS) return;
if (creation_step == CREATE_DELETE) {
if (!deleteDungeonDataFiles(&delete_result)) { state = DUNGEON_NOTICE; return; }
prepareNewDungeonWorldStorage();
creation_step = CREATE_GENERATE;
creation_step_at = now_ms;
return;
}
if (creation_step == CREATE_GENERATE) {
if (!generateDungeonWorld(&creation_generation_stats)) { state = DUNGEON_NOTICE; return; }
creation_step = CREATE_WORLD;
creation_step_at = now_ms;
return;
}
if (creation_step == CREATE_WORLD) {
if (!saveDungeonWorld(&storage_result)) { state = DUNGEON_NOTICE; return; }
creation_step = CREATE_PLAYER;
creation_step_at = now_ms;
return;
}
if (creation_step == CREATE_PLAYER) {
if (!resetDungeonPlayer(&player_result)) { state = DUNGEON_NOTICE; return; }
creation_step = CREATE_EXPLORED;
creation_step_at = now_ms;
return;
}
if (creation_step == CREATE_EXPLORED) {
if (!resetDungeonExplored(&getDungeonPlayerState()->position, &explored_result)) { state = DUNGEON_NOTICE; return; }
player_position = getDungeonPlayerState()->position;
creation_step = CREATE_READY;
creation_ready_at = now_ms;
creation_step_at = now_ms;
return;
}
if (now_ms - creation_ready_at < DUNGEON_CREATION_READY_MS) return;
if (!prepareRoomImage(&player_position)) { player_result.error = DUNGEON_PLAYER_INVALID_POSITION; state = DUNGEON_NOTICE; return; }
state = DUNGEON_ROOM;
startPlayTimer(now_ms);
image_visible = false;
decode_pending = true;
}
static void completeCombatVictory(void)
{
DungeonCell *cell = &getDungeonWorld()->floors[player_position.floor].cells[player_position.y][player_position.x];
DungeonPlayerState *player = getDungeonPlayerState();
const DungeonPlayerState previous_player = *player;
const uint16_t previous_flags = cell->stateFlags;
const DungeonPosition *exit = &getDungeonWorld()->exit;
const bool final_dragon = combat_state.enemyType == DUNGEON_ENEMY_DRAGON && player_position.floor == exit->floor && player_position.x == exit->x && player_position.y == exit->y;
combat_levels_gained = addDungeonPlayerExperience(combat_state.xpReward);
cell->stateFlags |= DUNGEON_STATE_COMPLETED;
if (final_dragon) {
cell->stateFlags |= DUNGEON_STATE_MESSAGE_READ | DUNGEON_STATE_UNLOCKED;
player->runFlags |= DUNGEON_RUN_COMPLETED;
}
if (!saveDungeonWorld(&storage_result) || !saveDungeonPlayer(&player_result)) {
*player = previous_player;
cell->stateFlags = previous_flags;
saveDungeonWorld(&storage_result);
state = DUNGEON_NOTICE;
return;
}
if (final_dragon) {
recordDungeonCompletion(player, &history_result, &completion_rank);
state = DUNGEON_COMPLETE;
return;
}
if (combat_levels_gained > 0U) snprintf(combat_text, sizeof(combat_text), "+%lu XP LEVEL +%u", (unsigned long)combat_state.xpReward, (unsigned)combat_levels_gained);
else snprintf(combat_text, sizeof(combat_text), "+%lu XP", (unsigned long)combat_state.xpReward);
combat_victory = true;
combat_result_title = "VICTORY";
combat_result_color = COLOR_GREEN;
state = DUNGEON_COMBAT_RESULT;
}
static void respawnAfterCombatDefeat(void)
{
DungeonPlayerState *player = getDungeonPlayerState();
const DungeonPlayerState previous_player = *player;
DungeonPosition respawn = getDungeonWorld()->entrance;
if (player_position.floor > 0U) respawn = getDungeonWorld()->stairLinks[player_position.floor - 1U].lower;
player->position = respawn;
player->health = (uint16_t)(player->maxHealth / 2U + player->maxHealth % 2U);
if (!saveDungeonPlayer(&player_result)) {
*player = previous_player;
state = DUNGEON_NOTICE;
return;
}
player_position = respawn;
recordExplored(&player_position);
snprintf(combat_text, sizeof(combat_text), "RETURNED WITH HALF HEALTH");
combat_victory = false;
combat_result_title = "DEFEATED";
combat_result_color = COLOR_RED;
state = DUNGEON_COMBAT_RESULT;
}
static void resolveCombatAction(void)
{
DungeonPlayerState *player = getDungeonPlayerState();
DungeonCombatResult result;
if (selected_combat_option == COMBAT_RUN) {
result = dungeonCombatAttemptRun(&combat_state, player);
if (result == DUNGEON_COMBAT_ESCAPED) {
player->position = encounter_source;
if (!saveDungeonPlayer(&player_result)) { state = DUNGEON_NOTICE; return; }
player_position = encounter_source;
snprintf(combat_text, sizeof(combat_text), "ESCAPED");
combat_victory = false;
combat_result_title = "ESCAPED";
combat_result_color = COLOR_CYAN;
state = DUNGEON_COMBAT_RESULT;
return;
}
snprintf(combat_text, sizeof(combat_text), "RUN FAILED ENEMY %lu", (unsigned long)combat_state.lastEnemyDamage);
} else if (selected_combat_option == COMBAT_POTION) {
result = dungeonCombatUsePotion(&combat_state, player);
snprintf(combat_text, sizeof(combat_text), "POTION ENEMY %lu", (unsigned long)combat_state.lastEnemyDamage);
} else {
result = dungeonCombatAttack(&combat_state, player);
snprintf(combat_text, sizeof(combat_text), "YOU %lu ENEMY %lu", (unsigned long)combat_state.lastPlayerDamage, (unsigned long)combat_state.lastEnemyDamage);
}
if (result == DUNGEON_COMBAT_ENEMY_DEFEATED) { completeCombatVictory(); return; }
if (result == DUNGEON_COMBAT_PLAYER_DEFEATED) { respawnAfterCombatDefeat(); return; }
if (selected_combat_option >= combatOptionCount()) selected_combat_option = COMBAT_ATTACK;
if (!saveDungeonPlayer(&player_result)) state = DUNGEON_NOTICE;
}
static uint32_t shopUpgradeCost(uint32_t base_cost)
{
const DungeonConfig *config = getDungeonConfig();
uint32_t cost = base_cost;
for (uint16_t purchase = 0U; purchase < getDungeonPlayerState()->shopPurchaseCount; ++purchase) {
const uint64_t increased = ((uint64_t)cost * (100U + config->shopCostIncreasePercent) + 99U) / 100U;
if (increased >= UINT32_MAX) return UINT32_MAX;
cost = (uint32_t)increased;
}
return cost;
}
static uint32_t shopChoiceCost(unsigned choice)
{
const DungeonConfig *config = getDungeonConfig();
if (choice == 0U) return config->shopRestoreCost;
if (choice == 1U) return shopUpgradeCost(config->shopHealthBaseCost);
if (choice == 2U) return shopUpgradeCost(config->shopAttackBaseCost);
return choice == 3U ? shopUpgradeCost(config->shopDefenceBaseCost) : 0U;
}
static void applyShopChoice(void)
{
DungeonPlayerState *player = getDungeonPlayerState();
const DungeonPlayerState previous_player = *player;
const DungeonConfig *config = getDungeonConfig();
if (selected_shop_option == 4U) { returnToRoom(); return; }
const uint32_t cost = shopChoiceCost(selected_shop_option);
if (player->gold < cost) { snprintf(shop_text, sizeof(shop_text), "NOT ENOUGH GOLD"); return; }
if (selected_shop_option == 0U) {
if (player->health >= player->maxHealth) { snprintf(shop_text, sizeof(shop_text), "HEALTH ALREADY FULL"); return; }
player->health = player->maxHealth;
snprintf(shop_text, sizeof(shop_text), "HEALTH RESTORED");
} else if (selected_shop_option == 1U) {
if (config->shopHealthGain == 0U || config->shopHealthGain > UINT16_MAX - player->maxHealth) { snprintf(shop_text, sizeof(shop_text), "MAX HP LIMIT"); return; }
player->maxHealth += config->shopHealthGain;
player->health = player->health <= UINT16_MAX - config->shopHealthGain ? player->health + config->shopHealthGain : UINT16_MAX;
snprintf(shop_text, sizeof(shop_text), "MAX HP INCREASED");
} else if (selected_shop_option == 2U) {
if (config->shopAttackGain == 0U || config->shopAttackGain > UINT16_MAX - player->attack) { snprintf(shop_text, sizeof(shop_text), "ATTACK LIMIT"); return; }
player->attack += config->shopAttackGain;
snprintf(shop_text, sizeof(shop_text), "ATTACK INCREASED");
} else {
if (config->shopDefenceGain == 0U || config->shopDefenceGain > UINT16_MAX - player->defence) { snprintf(shop_text, sizeof(shop_text), "DEFENCE LIMIT"); return; }
player->defence += config->shopDefenceGain;
snprintf(shop_text, sizeof(shop_text), "DEFENCE INCREASED");
}
player->gold -= cost;
if (player->shopPurchaseCount < UINT16_MAX) ++player->shopPurchaseCount;
if (!saveDungeonPlayer(&player_result)) {
*player = previous_player;
state = DUNGEON_NOTICE;
}
}
static unsigned aiChooseShopOption(void)
{
const DungeonPlayerState *player = getDungeonPlayerState();
const DungeonConfig *config = getDungeonConfig();
if ((uint32_t)player->health * 2U < player->maxHealth &&
player->health < player->maxHealth && player->gold >= shopChoiceCost(0U)) return 0U;
const unsigned options[] = {1U, 2U, 3U};
const uint8_t weights[] = {2U, 1U, 4U};
uint8_t total = 0U;
for (unsigned index = 0U; index < 3U; ++index) {
const unsigned option = options[index];
const bool valid = option == 1U ? config->shopHealthGain > 0U && config->shopHealthGain <= UINT16_MAX - player->maxHealth :
option == 2U ? config->shopAttackGain > 0U && config->shopAttackGain <= UINT16_MAX - player->attack :
config->shopDefenceGain > 0U && config->shopDefenceGain <= UINT16_MAX - player->defence;
if (valid && player->gold >= shopChoiceCost(option) && option != ai_last_shop_option) total += weights[index];
}
if (total == 0U) return 4U;
uint8_t ticket = (uint8_t)(esp_random() % total);
for (unsigned index = 0U; index < 3U; ++index) {
const unsigned option = options[index];
const bool valid = option == 1U ? config->shopHealthGain > 0U && config->shopHealthGain <= UINT16_MAX - player->maxHealth :
option == 2U ? config->shopAttackGain > 0U && config->shopAttackGain <= UINT16_MAX - player->attack :
config->shopDefenceGain > 0U && config->shopDefenceGain <= UINT16_MAX - player->defence;
if (!valid || player->gold < shopChoiceCost(option) || option == ai_last_shop_option) continue;
if (ticket < weights[index]) return option;
ticket = (uint8_t)(ticket - weights[index]);
}
return 4U;
}
/* -------------------------------------------------------------------------- */
/* Button actions: A/K action, C map/shop, E status */
/* -------------------------------------------------------------------------- */
void dungeonAction(bool action, bool map_action, bool status_action, bool context_action, uint32_t now_ms)
{
if (!running) return;
if (state == DUNGEON_COMPLETE && action) { menu_back(now_ms); return; }
if (state == DUNGEON_HISTORY && action) { state = DUNGEON_START_MENU; return; }
if (state == DUNGEON_SHOP) {
if (status_action) { returnToRoom(); return; }
if (action) applyShopChoice();
return;
}
if (state == DUNGEON_LEVEL_BONUS) {
if (status_action) { returnToRoom(); return; }
if (action) {
DungeonPlayerState *player = getDungeonPlayerState();
const DungeonPlayerState previous_player = *player;
if (applyDungeonPlayerStatChoice((uint8_t)selected_stat_option) && !saveDungeonPlayer(&player_result)) {
*player = previous_player;
state = DUNGEON_NOTICE;
} else if (player->unspentStatPoints == 0U) returnToRoom();
}
return;
}
if (state == DUNGEON_LOCKED_DOOR && action) {
DungeonPlayerState *player = getDungeonPlayerState();
player->position = encounter_source;
if (!saveDungeonPlayer(&player_result)) { state = DUNGEON_NOTICE; return; }
player_position = encounter_source;
returnToRoom();
return;
}
if (state == DUNGEON_TRAP_RESULT && action) {
if (!trap_defeated && startRoomEvent(&player_position)) return;
returnToRoom();
return;
}
if (state == DUNGEON_COMBAT_RESULT && action) {
if (combat_victory && startRoomEvent(&player_position)) return;
returnToRoom();
return;
}
if (state == DUNGEON_COMBAT && action) { resolveCombatAction(); return; }
if (state == DUNGEON_REWARD && action) {
returnToRoom();
return;
}
if (state == DUNGEON_ROOM && map_action) {
if (map_visible) {
map_visible = false;
waiting_for_centre = true;
returnToRoom();
return;
}
}
if (state == DUNGEON_ROOM && status_visible && context_action && status_page == 0U && getDungeonPlayerState()->unspentStatPoints > 0U) {
status_visible = false;
selected_stat_option = 0U;
state = DUNGEON_LEVEL_BONUS;
return;
}
if (state == DUNGEON_ROOM && !map_visible && !status_visible && context_action && isShopRoom(&getDungeonWorld()->floors[player_position.floor].cells[player_position.y][player_position.x])) {
selected_shop_option = 0U;
shop_text[0] = '\0';
state = DUNGEON_SHOP;
image_visible = false;
decode_pending = false;
return;
}
if (state == DUNGEON_ROOM && status_action) {
map_visible = false;
status_visible = !status_visible;
if (!status_visible) {
image_visible = false;
decode_pending = true;
}
waiting_for_centre = true;
return;
}
if (state == DUNGEON_ROOM && status_visible) {
if (action) status_page = (status_page + 1U) % 3U;
return;
}
if (state == DUNGEON_ROOM && map_visible) {
if (action) {
if (map_page == 0U) map_page = 1U;
else if (map_page == 1U) map_page = 3U;
else if (map_page == 3U) map_page = 2U;
else {
map_page = 0U;
map_floor = (map_floor + 1U) % DUNGEON_FLOOR_COUNT;
}
}
waiting_for_centre = true;
return;
}
if (state == DUNGEON_ROOM && map_action) {
map_floor = player_position.floor;
map_page = (player_position.y / 20U) * 2U + (player_position.x / 20U);
map_visible = true;
waiting_for_centre = true;
return;
}
if (!action) return;
if (state == DUNGEON_START_MENU && storage_result.error != DUNGEON_STORAGE_OK) {
traceDungeonCreation("A RECEIVED", 0, 0);
beginDungeonCreation(true, now_ms);
return;
}
if (state == DUNGEON_START_MENU && storage_result.error == DUNGEON_STORAGE_OK) {
if (selected_option == 0U) {
if (!prepareDungeonPlayer(&player_result)) {
state = DUNGEON_NOTICE;
return;
}
state = DUNGEON_ROOM;
player_position = getDungeonPlayerState()->position;
if (!prepareRoomImage(&player_position)) {
state = DUNGEON_NOTICE;
player_result.error = DUNGEON_PLAYER_INVALID_POSITION;
return;
}
if (!prepareDungeonExplored(&player_position, &explored_result)) {
state = DUNGEON_NOTICE;
return;
}
startPlayTimer(now_ms);
if (startRoomEvent(&player_position)) return;
image_visible = false;
decode_pending = true;
} else if (selected_option == 1U) {
beginDungeonCreation(false, now_ms);
return;
} else if (selected_option == 2U) {
ai_mode = true;
ai_start_at = now_ms;
ai_start_seconds = 2U;
state = DUNGEON_AI_START;
} else if (selected_option == 3U) {
if (!loadDungeonHistory(&history_result) && history_result.error == DUNGEON_HISTORY_OPEN_READ_FAILED) initDungeonHistory();
state = DUNGEON_HISTORY;
} else if (selected_option == 4U) {
menu_back(now_ms);
} else {
state = DUNGEON_NOTICE;
}
} else if (state == DUNGEON_NOTICE) {
state = DUNGEON_START_MENU;
}
}
void dungeonAIControl(bool speed_action, uint32_t now_ms)
{
if (!running || !ai_mode || state != DUNGEON_ROOM || map_visible || status_visible) return;
if (speed_action) {
cycleAISpeed(&ai_speed_index);
ai_last_move = now_ms;
}
}
/* -------------------------------------------------------------------------- */
/* Joystick movement and menu selection */
/* -------------------------------------------------------------------------- */
void updateDungeon(uint16_t *frame, int x_offset, int y_offset, bool joystick_valid, uint32_t now_ms)
{
if (!running) return;
commitPlayTime(now_ms);
if (state == DUNGEON_AI_START) {
if (!ai_mode) {
state = DUNGEON_START_MENU;
return;
}
const uint32_t elapsed = now_ms - ai_start_at;
if (elapsed < 2000U) {
ai_start_seconds = (uint8_t)((2000U - elapsed + 999U) / 1000U);
return;
}
state = DUNGEON_START_MENU;
selected_option = 1U;
dungeonAction(true, false, false, false, now_ms);
return;
}
if (state == DUNGEON_CREATING) {
advanceDungeonCreation(now_ms);
return;
}
if (decode_pending) {
decode_pending = false;
image_visible = imageDecodeFile(tile_path, frame);
}
if (ai_mode && state == DUNGEON_ROOM && !map_visible && !status_visible) {
if (isShopRoom(&getDungeonWorld()->floors[player_position.floor].cells[player_position.y][player_position.x]) && !ai_shop_visited) {
ai_shop_visited = true;
dungeonAction(false, false, false, true, now_ms);
return;
}
if (now_ms - ai_last_move >= aiSpeedDelay(ai_speed_index)) {
ai_last_move = now_ms;
aiMoveOnce();
}
return;
}
if (ai_mode && (state == DUNGEON_REWARD || state == DUNGEON_TRAP_RESULT)) {
if (now_ms - ai_last_move >= aiSpeedDelay(ai_speed_index)) {
ai_last_move = now_ms;
dungeonAction(true, false, false, false, now_ms);
}
return;
}
if (ai_mode && state == DUNGEON_COMBAT) {
if (now_ms - ai_last_move >= aiSpeedDelay(ai_speed_index)) {
ai_last_move = now_ms;
aiChooseCombatAction();
dungeonAction(true, false, false, false, now_ms);
}
return;
}
if (ai_mode && state == DUNGEON_COMBAT_RESULT) {
if (now_ms - ai_last_move >= aiSpeedDelay(ai_speed_index)) {
ai_last_move = now_ms;
dungeonAction(true, false, false, false, now_ms);
}
return;
}
if (ai_mode && state == DUNGEON_LOCKED_DOOR) {
if (now_ms - ai_last_move >= aiSpeedDelay(ai_speed_index)) {
ai_last_move = now_ms;
dungeonAction(true, false, false, false, now_ms);
}
return;
}
if (ai_mode && state == DUNGEON_SHOP) {
if (now_ms - ai_last_move >= aiSpeedDelay(ai_speed_index)) {
ai_last_move = now_ms;
selected_shop_option = aiChooseShopOption();
if (selected_shop_option != 4U) ai_last_shop_option = selected_shop_option;
dungeonAction(true, false, selected_shop_option == 4U, false, now_ms);
}
return;
}
if ((state != DUNGEON_START_MENU && state != DUNGEON_ROOM && state != DUNGEON_COMBAT && state != DUNGEON_LEVEL_BONUS && state != DUNGEON_SHOP) || !joystick_valid || map_visible || status_visible) return;
const int x_magnitude = x_offset < 0 ? -x_offset : x_offset;
const int y_magnitude = y_offset < 0 ? -y_offset : y_offset;
if (x_magnitude <= JOYSTICK_RELEASE && y_magnitude <= JOYSTICK_RELEASE) {
waiting_for_centre = false;
held_direction = 0;
repeating = false;
return;
}
if (waiting_for_centre) return;
if (state == DUNGEON_ROOM) {
if (x_magnitude < JOYSTICK_ENTER && y_magnitude < JOYSTICK_ENTER) return;
if (x_offset >= JOYSTICK_ENTER && y_magnitude >= JOYSTICK_ENTER) {
moveStairs(y_offset > 0);
waiting_for_centre = true;
return;
}
uint8_t exit;
if (x_magnitude >= y_magnitude) exit = x_offset > 0 ? DUNGEON_EXIT_EAST : DUNGEON_EXIT_WEST;
else exit = y_offset > 0 ? DUNGEON_EXIT_NORTH : DUNGEON_EXIT_SOUTH;
movePlayer(exit);
waiting_for_centre = true;
return;
}
if (y_magnitude < JOYSTICK_ENTER) return;
const int direction = y_offset > 0 ? -1 : 1;
if (direction == held_direction && now_ms - last_move < (repeating ? REPEAT_MS : REPEAT_START_MS)) return;
repeating = direction == held_direction;
held_direction = direction;
last_move = now_ms;
if (state == DUNGEON_COMBAT) {
const unsigned count = combatOptionCount();
if (direction < 0 && selected_combat_option > 0U) --selected_combat_option;
if (direction > 0 && selected_combat_option + 1U < count) ++selected_combat_option;
return;
}
if (state == DUNGEON_LEVEL_BONUS) {
if (direction < 0 && selected_stat_option > 0U) --selected_stat_option;
if (direction > 0 && selected_stat_option < 2U) ++selected_stat_option;
return;
}
if (state == DUNGEON_SHOP) {
if (direction < 0 && selected_shop_option > 0U) --selected_shop_option;
if (direction > 0 && selected_shop_option < 4U) ++selected_shop_option;
return;
}
const unsigned count = sizeof(start_options) / sizeof(start_options[0]);
if (direction < 0 && selected_option > 0U) --selected_option;
if (direction > 0 && selected_option + 1U < count) ++selected_option;
}
/* -------------------------------------------------------------------------- */
/* Room overlay icons */
/* -------------------------------------------------------------------------- */
static void drawBoldLine(uint16_t *frame, int x0, int y0, int x1, int y1, uint16_t color)
{
draw_line(frame, x0, y0, x1, y1, color);
draw_line(frame, x0 + 1, y0, x1 + 1, y1, color);
draw_line(frame, x0, y0 + 1, x1, y1 + 1, color);
}
static bool exitDestinationExplored(uint8_t exit)
{
int x = player_position.x;
int y = player_position.y;
if (exit == DUNGEON_EXIT_NORTH) --y;
else if (exit == DUNGEON_EXIT_EAST) ++x;
else if (exit == DUNGEON_EXIT_SOUTH) ++y;
else if (exit == DUNGEON_EXIT_WEST) --x;
return x >= 0 && x < (int)DUNGEON_WIDTH && y >= 0 && y < (int)DUNGEON_HEIGHT &&
isDungeonExplored(player_position.floor, (uint8_t)x, (uint8_t)y);
}
static void drawExitIcon(uint16_t *frame, uint8_t exit, uint16_t color)
{
int x = 160;
int y = 85;
if (exit == DUNGEON_EXIT_NORTH) y = 12;
else if (exit == DUNGEON_EXIT_EAST) x = LCD_WIDTH - 13;
else if (exit == DUNGEON_EXIT_SOUTH) y = 157;
else if (exit == DUNGEON_EXIT_WEST) x = 12;
if (exit == DUNGEON_EXIT_NORTH || exit == DUNGEON_EXIT_SOUTH) {
const int direction = exit == DUNGEON_EXIT_NORTH ? -1 : 1;
drawBoldLine(frame, x, y - direction * 7, x, y + direction * 7, color);
drawBoldLine(frame, x, y + direction * 7, x - 5, y + direction * 2, color);
drawBoldLine(frame, x, y + direction * 7, x + 5, y + direction * 2, color);
} else {
const int direction = exit == DUNGEON_EXIT_EAST ? 1 : -1;
drawBoldLine(frame, x - direction * 7, y, x + direction * 7, y, color);
drawBoldLine(frame, x + direction * 7, y, x + direction * 2, y - 5, color);
drawBoldLine(frame, x + direction * 7, y, x + direction * 2, y + 5, color);
}
}
static void drawStairIcon(uint16_t *frame, bool up)
{
const int x = LCD_WIDTH - 44;
const int y = up ? 12 : 157;
const int direction = up ? -1 : 1;
drawBoldLine(frame, x, y - direction * 7, x, y + direction * 7, COLOR_YELLOW);
drawBoldLine(frame, x, y + direction * 7, x - 5, y + direction * 2, COLOR_YELLOW);
drawBoldLine(frame, x, y + direction * 7, x + 5, y + direction * 2, COLOR_YELLOW);
}
static void drawShopIndicator(uint16_t *frame)
{
draw_text(frame, 5, 5, "C SHOP", 1, COLOR_YELLOW);
}
static uint16_t mapCellColor(const DungeonCell *cell)
{
if (cell->tileType == DUNGEON_TILE_STAIRS_UP || cell->tileType == DUNGEON_TILE_STAIRS_DOWN || cell->tileType == DUNGEON_TILE_STAIRS_BOTH) return COLOR_PURPLE;
if (cell->tileType == DUNGEON_TILE_ENTRANCE) return COLOR_GREEN;
if (cell->tileType == DUNGEON_TILE_EXIT) return COLOR_MAGENTA;
if (cell->tileType == DUNGEON_TILE_PASSAGE || cell->tileType == DUNGEON_TILE_DEAD_END_PASSAGE) return COLOR_LIGHTGRAY;
return COLOR_CYAN;
}
/* -------------------------------------------------------------------------- */
/* Explored-map drawing */
/* -------------------------------------------------------------------------- */
static void drawMapCell(uint16_t *frame, unsigned x, unsigned y, unsigned first_column, unsigned first_row, const DungeonCell *cell)
{
const int screen_x = ((int)x - (int)first_column) * 8;
const int screen_y = ((int)y - (int)first_row) * 8;
const uint16_t color = mapCellColor(cell);
const bool large = cell->tileType != DUNGEON_TILE_PASSAGE && cell->tileType != DUNGEON_TILE_DEAD_END_PASSAGE;
fill_rect(frame, screen_x + (large ? 1 : 2), screen_y + (large ? 1 : 2), large ? 6 : 4, large ? 6 : 4, color);
if ((cell->exits & DUNGEON_EXIT_NORTH) != 0U) {
const bool known = y > 0U && isDungeonExplored((uint8_t)map_floor, (uint8_t)x, (uint8_t)(y - 1U));
fill_rect(frame, screen_x + 3, screen_y, 2, 4, known ? color : COLOR_ORANGE);
if (!known) fill_rect(frame, screen_x + 2, screen_y, 4, 2, COLOR_ORANGE);
}
if ((cell->exits & DUNGEON_EXIT_EAST) != 0U) {
const bool known = x + 1U < DUNGEON_WIDTH && isDungeonExplored((uint8_t)map_floor, (uint8_t)(x + 1U), (uint8_t)y);
fill_rect(frame, screen_x + 4, screen_y + 3, 4, 2, known ? color : COLOR_ORANGE);
if (!known) fill_rect(frame, screen_x + 6, screen_y + 2, 2, 4, COLOR_ORANGE);
}
if ((cell->exits & DUNGEON_EXIT_SOUTH) != 0U) {
const bool known = y + 1U < DUNGEON_HEIGHT && isDungeonExplored((uint8_t)map_floor, (uint8_t)x, (uint8_t)(y + 1U));
fill_rect(frame, screen_x + 3, screen_y + 4, 2, 4, known ? color : COLOR_ORANGE);
if (!known) fill_rect(frame, screen_x + 2, screen_y + 6, 4, 2, COLOR_ORANGE);
}
if ((cell->exits & DUNGEON_EXIT_WEST) != 0U) {
const bool known = x > 0U && isDungeonExplored((uint8_t)map_floor, (uint8_t)(x - 1U), (uint8_t)y);
fill_rect(frame, screen_x, screen_y + 3, 4, 2, known ? color : COLOR_ORANGE);
if (!known) fill_rect(frame, screen_x, screen_y + 2, 2, 4, COLOR_ORANGE);
}
}
static void drawMapKeyItem(uint16_t *frame, int y, uint16_t color, const char *label)
{
fill_rect(frame, 174, y, 10, 10, color);
drawPet2001TextTransparent(frame, 190, y + 1, label, COLOR_WHITE, 1);
}
static void drawMapSidePanel(uint16_t *frame)
{
draw_line(frame, 164, 0, 164, 160, COLOR_DARKGRAY);
drawPet2001TextTransparent(frame, 174, 5, "KEY", COLOR_CYAN, 2);
drawMapKeyItem(frame, 31, COLOR_YELLOW, "PLR");
drawMapKeyItem(frame, 47, COLOR_LIGHTGRAY, "PATH");
drawMapKeyItem(frame, 63, COLOR_CYAN, "ROOM");
drawMapKeyItem(frame, 79, COLOR_ORANGE, "UNEXP EXIT");
drawMapKeyItem(frame, 95, COLOR_PURPLE, "STAIRS");
drawMapKeyItem(frame, 111, COLOR_GREEN, "ENTRANCE");
drawMapKeyItem(frame, 127, COLOR_RED, "KEY");
drawPet2001TextCentredTransparent(frame, 247, 3, "N", COLOR_WHITE, 1);
drawPet2001TextCentredTransparent(frame, 247, 35, "S", COLOR_WHITE, 1);
drawPet2001TextCentredTransparent(frame, 232, 19, "W", COLOR_WHITE, 1);
drawPet2001TextCentredTransparent(frame, 262, 19, "E", COLOR_WHITE, 1);
draw_line(frame, 239, 23, 255, 23, COLOR_LIGHTGRAY);
draw_line(frame, 247, 15, 247, 31, COLOR_LIGHTGRAY);
draw_line(frame, 247, 13, 243, 20, COLOR_RED);
draw_line(frame, 247, 13, 251, 20, COLOR_RED);
draw_rect(frame, 270, 5, 40, 40, COLOR_WHITE);
draw_line(frame, 290, 5, 290, 45, COLOR_WHITE);
draw_line(frame, 270, 25, 310, 25, COLOR_WHITE);
for (unsigned page = 0; page < 4U; ++page) {
const int x = 270 + (int)(page % 2U) * 20;
const int y = 5 + (int)(page / 2U) * 20;
const bool selected = page == map_page;
fill_rect(frame, x + 2, y + 2, 17, 17, selected ? COLOR_YELLOW : COLOR_DARKGRAY);
char number[2] = { (char)('1' + page), '\0' };
drawPet2001TextCentredTransparent(frame, x + 10, y + 6, number,
selected ? COLOR_BLACK : COLOR_WHITE, 1);
}
const unsigned player_page = (player_position.y / 20U) * 2U + player_position.x / 20U;
if (player_position.floor == map_floor && player_page != map_page) {
const int x = 270 + (int)(player_page % 2U) * 20;
const int y = 5 + (int)(player_page / 2U) * 20;
fill_circle(frame, x + 15, y + 15, 2, COLOR_YELLOW);
}
}
static void drawDungeonMap(uint16_t *frame)
{
const unsigned first_column = (map_page % 2U) * 20U;
const unsigned first_row = (map_page / 2U) * 20U;
const DungeonFloor *floor = &getDungeonWorld()->floors[map_floor];
clear(frame, COLOR_BLACK);
for (unsigned y = first_row; y < first_row + 20U; ++y)
for (unsigned x = first_column; x < first_column + 20U; ++x)
if (isDungeonExplored((uint8_t)map_floor, (uint8_t)x, (uint8_t)y)) drawMapCell(frame, x, y, first_column, first_row, &floor->cells[y][x]);
if ((getDungeonPlayerState()->runFlags & DUNGEON_RUN_KEY_REVEALED) != 0U) {
bool key_drawn = false;
for (unsigned y = 0; y < DUNGEON_HEIGHT && !key_drawn; ++y) {
for (unsigned x = 0; x < DUNGEON_WIDTH; ++x) {
const DungeonCell *cell = &floor->cells[y][x];
if ((cell->eventFlags & DUNGEON_EVENT_KEY) == 0U ||
(cell->stateFlags & DUNGEON_STATE_KEY_COLLECTED) != 0U) continue;
if (x >= first_column && x < first_column + 20U &&
y >= first_row && y < first_row + 20U)
fill_rect(frame, ((int)x - (int)first_column) * 8 + 1,
((int)y - (int)first_row) * 8 + 1, 6, 6, COLOR_RED);
key_drawn = true;
break;
}
}
}
if (player_position.floor == map_floor && player_position.x >= first_column && player_position.x < first_column + 20U && player_position.y >= first_row && player_position.y < first_row + 20U)
fill_rect(frame, ((int)player_position.x - (int)first_column) * 8 + 1, ((int)player_position.y - (int)first_row) * 8 + 1, 6, 6, COLOR_YELLOW);
drawMapSidePanel(frame);
char footer[20];
snprintf(footer, sizeof(footer), "FLOOR %u PAGE %u/4", map_floor + 1U, map_page + 1U);
drawPet2001TextTransparent(frame, 4, 161, footer, COLOR_WHITE, 1);
drawPet2001TextTransparent(frame, 172, 161, "A/K PAGE C CLOSE", COLOR_LIGHTGRAY, 1);
}
static bool isRoomTile(uint8_t tile_type)
{
return tile_type == DUNGEON_TILE_ROOM_OPEN ||
tile_type == DUNGEON_TILE_DEAD_END_ROOM_ROUND ||
tile_type == DUNGEON_TILE_DEAD_END_ROOM_SQUARE ||
tile_type == DUNGEON_TILE_DEAD_END_ROOM_ROUND_ALT;
}
static unsigned exploredRoomCount(unsigned floor_index, unsigned *total_rooms)
{
unsigned explored = 0;
unsigned total = 0;
for (unsigned y = 0; y < DUNGEON_HEIGHT; ++y)
for (unsigned x = 0; x < DUNGEON_WIDTH; ++x)
if (isRoomTile(getDungeonWorld()->floors[floor_index].cells[y][x].tileType)) {
++total;
if (isDungeonExplored((uint8_t)floor_index, (uint8_t)x, (uint8_t)y)) ++explored;
}
if (total_rooms != NULL) *total_rooms = total;
return explored;
}
static unsigned specialItemCount(uint32_t flags)
{
unsigned count = 0;
while (flags != 0U) {
count += flags & 1U;
flags >>= 1;
}
return count;
}
static void formatPlayedTime(char *text, size_t size, uint32_t total_seconds)
{
const uint32_t hours = total_seconds / 3600U;
const uint32_t minutes = (total_seconds / 60U) % 60U;
const uint32_t seconds = total_seconds % 60U;
snprintf(text, size, "%lu:%02lu:%02lu", (unsigned long)hours,
(unsigned long)minutes, (unsigned long)seconds);
}
/* -------------------------------------------------------------------------- */
/* Status, rewards, traps, shops, and combat screens */
/* -------------------------------------------------------------------------- */
static void drawStatusScreen(uint16_t *frame)
{
const DungeonPlayerState *player = getDungeonPlayerState();
char text[40];
clear(frame, COLOR_BLACK);
draw_text_centred(frame, 160, 5, status_page == 0U ? "STATUS" : status_page == 1U ? "INVENTORY" : "PROGRESS", 2, COLOR_CYAN);
snprintf(text, sizeof(text), "%u/3", status_page + 1U);
draw_text(frame, 292, 7, text, 1, COLOR_DARKGRAY);
if (status_page == 0U) {
snprintf(text, sizeof(text), "Lvl %u XP %lu/%lu", (unsigned)player->level, (unsigned long)player->experience, (unsigned long)dungeonExperienceNeeded(player->level)); draw_text(frame, 12, 35, text, 2, COLOR_WHITE);
snprintf(text, sizeof(text), "Health: %u/%u", (unsigned)player->health, (unsigned)player->maxHealth); draw_text(frame, 12, 58, text, 2, COLOR_WHITE);
snprintf(text, sizeof(text), "Attack: %u", (unsigned)player->attack); draw_text(frame, 12, 81, text, 2, COLOR_WHITE);
snprintf(text, sizeof(text), "Defence: %u", (unsigned)player->defence); draw_text(frame, 12, 104, text, 2, COLOR_WHITE);
snprintf(text, sizeof(text), "Level choices: %u", (unsigned)player->unspentStatPoints); draw_text(frame, 12, 127, text, 2, COLOR_WHITE);
} else if (status_page == 1U) {
snprintf(text, sizeof(text), "Gold: %lu", (unsigned long)player->gold); draw_text(frame, 12, 40, text, 2, COLOR_WHITE);
snprintf(text, sizeof(text), "Keys: %u", (unsigned)player->keys); draw_text(frame, 12, 67, text, 2, COLOR_WHITE);
snprintf(text, sizeof(text), "Health potions: %u", (unsigned)player->healthPotions); draw_text(frame, 12, 94, text, 2, COLOR_WHITE);
snprintf(text, sizeof(text), "Special items: %u", specialItemCount(player->inventoryFlags)); draw_text(frame, 12, 121, text, 2, COLOR_WHITE);
} else {
snprintf(text, sizeof(text), "MOVES %lu", (unsigned long)player->moves);
draw_text_centred(frame, 160, 26, text, 2, COLOR_CYAN);
formatPlayedTime(text, sizeof(text), player->timePlayedSeconds);
draw_text_centred(frame, 160, 47, text, 2, COLOR_WHITE);
for (unsigned floor = 0; floor < DUNGEON_FLOOR_COUNT; ++floor) {
unsigned total_rooms = 0U;
const unsigned explored_rooms = exploredRoomCount(floor, &total_rooms);
snprintf(text, sizeof(text), "Floor %u: %u/%u rooms", floor + 1U, explored_rooms, total_rooms);
draw_text_centred(frame, 160, 66 + (int)floor * 22, text, 2, COLOR_WHITE);
}
}
draw_text_centred(frame, 160, 153, status_page == 0U && player->unspentStatPoints > 0U ? "A/K NEXT C BONUS E CLOSE" : "A/K NEXT E CLOSE BOOT BACK", 1, COLOR_LIGHTGRAY);
}
static void drawRewardScreen(uint16_t *frame)
{
if (!image_visible) clear(frame, COLOR_BLACK);
drawPet2001TextCentredTransparent(frame, 160, 31, reward_is_key ? "KEY" : "TREASURE",
reward_is_key ? COLOR_CYAN : COLOR_YELLOW, 3);
drawPet2001TextCentredTransparent(frame, 160, 80, reward_text, COLOR_WHITE, 2);
drawPet2001TextCentredTransparent(frame, 160, 145, "A CONTINUE", COLOR_LIGHTGRAY, 1);
}
static void drawTrapResultScreen(uint16_t *frame)
{
char text[40];
if (!image_visible) clear(frame, COLOR_BLACK);
drawPet2001TextCentredTransparent(frame, 160, 8, trap_defeated ? "DEFEATED" : "TRAP!",
trap_defeated ? COLOR_RED : COLOR_YELLOW, 2);
if (trap_defeated) snprintf(text, sizeof(text), "RETURNED WITH HALF HEALTH");
else snprintf(text, sizeof(text), "DAMAGE %u", (unsigned)trap_damage);
drawPet2001TextCentredTransparent(frame, 160, 137, text, COLOR_YELLOW, 1);
drawPet2001TextCentredTransparent(frame, 160, 153, "A CONTINUE", COLOR_WHITE, 1);
}
static void drawLockedDoorScreen(uint16_t *frame)
{
clear(frame, COLOR_BLACK);
drawPet2001TextCentredTransparent(frame, 160, 37, "LOCKED DOOR", COLOR_RED, 3);
drawPet2001TextCentredTransparent(frame, 160, 85, "FIND THE KEY", COLOR_YELLOW, 2);
drawPet2001TextCentredTransparent(frame, 160, 145, "A RETURN", COLOR_LIGHTGRAY, 1);
}
static void drawLevelBonusScreen(uint16_t *frame)
{
static const char *options[] = { "HEALTH", "ATTACK", "DEFENCE" };
const DungeonPlayerState *player = getDungeonPlayerState();
char text[40];
clear(frame, COLOR_BLACK);
drawPet2001TextCentredTransparent(frame, 160, 16, "LEVEL BONUS", COLOR_CYAN, 2);
snprintf(text, sizeof(text), "CHOICES %u", (unsigned)player->unspentStatPoints);
drawPet2001TextCentredTransparent(frame, 160, 47, text, COLOR_WHITE, 1);
for (unsigned index = 0; index < 3U; ++index)
drawPet2001TextCentredTransparent(frame, 160, 70 + (int)index * 18, options[index],
index == selected_stat_option ? COLOR_YELLOW : COLOR_WHITE, 1);
drawPet2001TextCentredTransparent(frame, 160, 153, "UP/DOWN SELECT A CHOOSE E BACK",
COLOR_LIGHTGRAY, 1);
}
static void drawShopScreen(uint16_t *frame)
{
static const char *labels[] = { "RESTORE HP", "MAX HP +", "ATTACK +", "DEFENCE +", "LEAVE SHOP" };
char text[40];
clear(frame, COLOR_BLACK);
drawPet2001TextCentredTransparent(frame, 160, 8, "SHOP", COLOR_CYAN, 2);
snprintf(text, sizeof(text), "GOLD %lu", (unsigned long)getDungeonPlayerState()->gold);
drawPet2001TextCentredTransparent(frame, 160, 29, text, COLOR_YELLOW, 1);
for (unsigned index = 0; index < 5U; ++index) {
if (index < 4U) snprintf(text, sizeof(text), "%s %lu", labels[index], (unsigned long)shopChoiceCost(index));
else snprintf(text, sizeof(text), "%s", labels[index]);
drawPet2001TextCentredTransparent(frame, 160, 48 + (int)index * 17, text,
index == selected_shop_option ? COLOR_YELLOW : COLOR_WHITE, 1);
}
if (shop_text[0] != '\0') {
drawPet2001TextCentredTransparent(frame, 160, 137, shop_text, COLOR_CYAN, 1);
}
drawPet2001TextCentredTransparent(frame, 160, 153, "UP/DOWN SELECT A BUY E EXIT",
COLOR_LIGHTGRAY, 1);
}
static void drawCombatScreen(uint16_t *frame)
{
static const char *options[] = { "ATTACK", "RUN", "POTION" };
const DungeonPlayerState *player = getDungeonPlayerState();
char text[40];
if (!image_visible) clear(frame, COLOR_BLACK);
draw_text_centred(frame, 160, 5, combatEnemyName(combat_state.enemyType), 2, COLOR_RED);
draw_text(frame, 7, 34, "YOU", 1, COLOR_CYAN);
snprintf(text, sizeof(text), "HP %u/%u", (unsigned)player->health, (unsigned)player->maxHealth); draw_text(frame, 7, 49, text, 1, COLOR_WHITE);
snprintf(text, sizeof(text), "ATT %u", (unsigned)player->attack); draw_text(frame, 7, 63, text, 1, COLOR_WHITE);
snprintf(text, sizeof(text), "DEF %u", (unsigned)player->defence); draw_text(frame, 7, 77, text, 1, COLOR_WHITE);
draw_text(frame, 238, 34, "ENEMY", 1, COLOR_RED);
snprintf(text, sizeof(text), "HP %u/%u", (unsigned)combat_state.health, (unsigned)combat_state.maxHealth); draw_text(frame, 238, 49, text, 1, COLOR_WHITE);
snprintf(text, sizeof(text), "ATT %u", (unsigned)combat_state.attack); draw_text(frame, 238, 63, text, 1, COLOR_WHITE);
snprintf(text, sizeof(text), "DEF %u", (unsigned)combat_state.defence); draw_text(frame, 238, 77, text, 1, COLOR_WHITE);
if (combat_text[0] != '\0') draw_text_centred(frame, 160, 96, combat_text, 1, COLOR_YELLOW);
for (unsigned index = 0; index < combatOptionCount(); ++index)
draw_text_centred(frame, 160, 110 + (int)index * 14, options[index], 1, index == selected_combat_option ? COLOR_YELLOW : COLOR_WHITE);
draw_text_centred(frame, 160, 156, "UP/DOWN SELECT A CHOOSE", 1, COLOR_LIGHTGRAY);
}
static void drawCombatResultScreen(uint16_t *frame)
{
clear(frame, COLOR_BLACK);
drawPet2001TextCentredTransparent(frame, 160, 37,
combat_result_title == NULL ? "COMBAT" : combat_result_title,
combat_result_color, 3);
drawPet2001TextCentredTransparent(frame, 160, 88, combat_text, COLOR_WHITE, 1);
drawPet2001TextCentredTransparent(frame, 160, 145, "A CONTINUE", COLOR_LIGHTGRAY, 1);
}
static void drawCompleteScreen(uint16_t *frame)
{
const DungeonPlayerState *player = getDungeonPlayerState();
char text[40];
clear(frame, COLOR_BLACK);
drawPet2001TextCentredTransparent(frame, 160, 18, "YOU WIN", COLOR_GREEN, 3);
snprintf(text, sizeof(text), "LEVEL %u XP %lu", (unsigned)player->level, (unsigned long)player->experience);
drawPet2001TextCentredTransparent(frame, 160, 67, text, COLOR_WHITE, 1);
snprintf(text, sizeof(text), "GOLD %lu MOVES %lu", (unsigned long)player->gold, (unsigned long)player->moves);
drawPet2001TextCentredTransparent(frame, 160, 89, text, COLOR_YELLOW, 1);
if (history_result.error == DUNGEON_HISTORY_OK && completion_rank > 0U) {
snprintf(text, sizeof(text), "HALL OF FAME #%u", (unsigned)completion_rank);
drawPet2001TextCentredTransparent(frame, 160, 112, text, COLOR_CYAN, 1);
}
drawPet2001TextCentredTransparent(frame, 160, 145, "A RETURN TO GAMES", COLOR_LIGHTGRAY, 1);
}
static void drawHistoryScreen(uint16_t *frame)
{
char text[40];
clear(frame, COLOR_BLACK);
drawPet2001TextCentredTransparent(frame, 160, 7, "DUNGEON HALL OF FAME", COLOR_CYAN, 2);
if (history_result.error != DUNGEON_HISTORY_OK) {
const char *error_text = dungeonHistoryErrorText(history_result.error);
// PET at 2x fits 20 cells; retain longer checksum diagnostics in full.
const int error_scale = strlen(error_text) > 20U ? 1 : 2;
drawPet2001TextCentredTransparent(frame, 160, 76, error_text, COLOR_YELLOW, error_scale);
} else if (dungeonHistoryCount() == 0U) {
drawPet2001TextCentredTransparent(frame, 160, 76, "NO COMPLETIONS YET", COLOR_YELLOW, 2);
} else {
for (unsigned index = 0; index < dungeonHistoryCount(); ++index) {
const DungeonHistoryRecord *record = dungeonHistoryRecord((uint8_t)index);
snprintf(text, sizeof(text), "%u %luG L%u %luM", index + 1U,
(unsigned long)record->gold, (unsigned)record->level, (unsigned long)record->moves);
drawPet2001TextCentredTransparent(frame, 160, 32 + (int)index * 21, text,
index == 0U ? COLOR_YELLOW : COLOR_WHITE, 1);
}
}
drawPet2001TextCentredTransparent(frame, 160, 153, "A RETURN TO MENU BOOT BACK",
COLOR_LIGHTGRAY, 1);
}
/* -------------------------------------------------------------------------- */
/* Top-level Dungeon renderer */
/* -------------------------------------------------------------------------- */
void drawDungeon(uint16_t *frame)
{
if (!running || frame == NULL) return;
if (state == DUNGEON_COMBAT) {
drawCombatScreen(frame);
return;
}
if (state == DUNGEON_COMBAT_RESULT) {
drawCombatResultScreen(frame);
return;
}
if (state == DUNGEON_COMPLETE) {
drawCompleteScreen(frame);
return;
}
if (state == DUNGEON_HISTORY) {
drawHistoryScreen(frame);
return;
}
if (state == DUNGEON_TRAP_RESULT) {
drawTrapResultScreen(frame);
return;
}
if (state == DUNGEON_LOCKED_DOOR) {
drawLockedDoorScreen(frame);
return;
}
if (state == DUNGEON_LEVEL_BONUS) {
drawLevelBonusScreen(frame);
return;
}
if (state == DUNGEON_SHOP) {
drawShopScreen(frame);
return;
}
if (state == DUNGEON_REWARD) {
drawRewardScreen(frame);
return;
}
if (state == DUNGEON_ROOM && status_visible) {
drawStatusScreen(frame);
return;
}
if (state == DUNGEON_ROOM && map_visible) {
drawDungeonMap(frame);
return;
}
if (state == DUNGEON_ROOM && decode_pending) return;
if (state == DUNGEON_ROOM && image_visible) {
const DungeonCell *cell = &getDungeonWorld()->floors[player_position.floor].cells[player_position.y][player_position.x];
if ((cell->exits & DUNGEON_EXIT_NORTH) != 0U) drawExitIcon(frame, DUNGEON_EXIT_NORTH, exitDestinationExplored(DUNGEON_EXIT_NORTH) ? COLOR_GREEN : COLOR_CYAN);
if ((cell->exits & DUNGEON_EXIT_EAST) != 0U) drawExitIcon(frame, DUNGEON_EXIT_EAST, exitDestinationExplored(DUNGEON_EXIT_EAST) ? COLOR_GREEN : COLOR_CYAN);
if ((cell->exits & DUNGEON_EXIT_SOUTH) != 0U) drawExitIcon(frame, DUNGEON_EXIT_SOUTH, exitDestinationExplored(DUNGEON_EXIT_SOUTH) ? COLOR_GREEN : COLOR_CYAN);
if ((cell->exits & DUNGEON_EXIT_WEST) != 0U) drawExitIcon(frame, DUNGEON_EXIT_WEST, exitDestinationExplored(DUNGEON_EXIT_WEST) ? COLOR_GREEN : COLOR_CYAN);
if (stairAvailable(true)) drawStairIcon(frame, true);
if (stairAvailable(false)) drawStairIcon(frame, false);
if (isShopRoom(cell)) drawShopIndicator(frame);
if (ai_mode) {
char ai_text[28];
snprintf(ai_text, sizeof(ai_text), "AI %s E SPEED", aiSpeedShortText(ai_speed_index));
draw_text_centred(frame, 160, 156, ai_text, 1, COLOR_YELLOW);
}
return;
}
if (state == DUNGEON_START_MENU && decode_pending) {
// Avoid showing the previous screen during the one-frame JPEG decode.
clear(frame, COLOR_BLACK);
return;
}
// PET start-menu text is transparent, so retain the decoded JPEG beneath it.
if (state != DUNGEON_START_MENU || !image_visible) clear(frame, COLOR_BLACK);
drawPet2001TextCentredTransparent(frame, 160, 10, "DUNGEON", COLOR_CYAN, 3);
if (state == DUNGEON_CREATING) {
const char *checkpoint = "READY TO PLAY";
if (creation_step == CREATE_DELETE) checkpoint = "CLEARING OLD FILES";
else if (creation_step == CREATE_GENERATE) checkpoint = "MAP / ROOMS / KEYS";
else if (creation_step == CREATE_WORLD) checkpoint = "SAVING DUNGEON MAP";
else if (creation_step == CREATE_PLAYER) checkpoint = "PLAYER / LEVEL";
else if (creation_step == CREATE_EXPLORED) checkpoint = "EXPLORING MAP";
drawPet2001TextCentredTransparent(frame, 160, 43, "CREATING NEW DUNGEON", COLOR_WHITE, 2);
drawPet2001TextCentredTransparent(frame, 160, 82, checkpoint, COLOR_CYAN, 2);
drawPet2001TextCentredTransparent(frame, 160, 120, "PLEASE WAIT", COLOR_LIGHTGRAY, 1);
} else if (storage_result.error != DUNGEON_STORAGE_OK) {
drawPet2001TextCentredTransparent(frame, 160, 62, dungeonStorageErrorText(storage_result.error), COLOR_RED, 2);
drawPet2001TextCentredTransparent(frame, 160, 88, "/spiffs/dungeon.dat", COLOR_WHITE, 1);
drawPet2001TextCentredTransparent(frame, 160, 116, creation_trace[0] != '\0' ? creation_trace : "A ERASES + CREATES NEW", COLOR_YELLOW, 1);
} else if (state == DUNGEON_AI_START) {
char countdown[20];
drawPet2001TextCentredTransparent(frame, 160, 38, "AI MODE", COLOR_CYAN, 3);
drawPet2001TextCentredTransparent(frame, 160, 80, "NEW DUNGEON", COLOR_WHITE, 2);
snprintf(countdown, sizeof(countdown), "STARTING IN %u", (unsigned)ai_start_seconds);
drawPet2001TextCentredTransparent(frame, 160, 124, countdown, COLOR_LIGHTGRAY, 1);
} else if (state == DUNGEON_START_MENU) {
for (unsigned index = 0; index < sizeof(start_options) / sizeof(start_options[0]); ++index) {
drawPet2001TextCentredTransparent(frame, 160, 43 + (int)index * 21, start_options[index],
index == selected_option ? COLOR_CYAN : COLOR_WHITE,
index == selected_option ? 2 : 1);
}
} else if (state == DUNGEON_NOTICE) {
const char *notice_text = player_result.error != DUNGEON_PLAYER_OK ? dungeonPlayerErrorText(player_result.error) : explored_result.error != DUNGEON_EXPLORED_OK ? dungeonExploredErrorText(explored_result.error) : selected_option == 1U ? "NEW DUNGEON NEXT" : selected_option == 2U ? "AI MODE NEXT" : "BOOT RETURNS TO GAMES";
// PET at 2x fits 20 cells. Keep longer storage/player diagnostics visible.
const int notice_scale = strlen(notice_text) > 20U ? 1 : 2;
drawPet2001TextCentredTransparent(frame, 160, 67, notice_text, COLOR_YELLOW, notice_scale);
drawPet2001TextCentredTransparent(frame, 160, 92, creation_trace, COLOR_WHITE, 1);
drawPet2001TextCentredTransparent(frame, 160, 116, "A RETURNS TO MENU", COLOR_WHITE, 1);
} else {
drawPet2001TextCentredTransparent(frame, 160, 75, "ENTRANCE IMAGE FAILED", COLOR_RED, 1);
drawPet2001TextCentredTransparent(frame, 160, 104, tile_path, COLOR_WHITE, 1);
}
if (state != DUNGEON_CREATING) {
drawPet2001TextCentredTransparent(frame, 160, 156, "UP/DOWN SELECT A OPEN BOOT BACK",
COLOR_LIGHTGRAY, 1);
}
}