feat(debug): Add autonomous testing toolkit with CLI args, bot agent, state dump, and screenshots
This commit is contained in:
parent
2730ff6720
commit
2d8e702bf3
7 changed files with 864 additions and 3 deletions
5
.gitignore
vendored
5
.gitignore
vendored
|
|
@ -22,6 +22,11 @@ err.txt
|
||||||
out.txt
|
out.txt
|
||||||
run_log.txt
|
run_log.txt
|
||||||
|
|
||||||
|
# Debugging, Autonomous Testing & Screenshots
|
||||||
|
screenshots/
|
||||||
|
*.png
|
||||||
|
*.json
|
||||||
|
|
||||||
# OS / Editor files
|
# OS / Editor files
|
||||||
.DS_Store
|
.DS_Store
|
||||||
Thumbs.db
|
Thumbs.db
|
||||||
|
|
|
||||||
|
|
@ -37,6 +37,7 @@ add_executable(DoorNroll
|
||||||
src/hub/HubRoom.cpp
|
src/hub/HubRoom.cpp
|
||||||
src/hub/RestRoom.cpp
|
src/hub/RestRoom.cpp
|
||||||
src/core/SaveSystem.cpp
|
src/core/SaveSystem.cpp
|
||||||
|
src/debug/DebugSystem.cpp
|
||||||
)
|
)
|
||||||
|
|
||||||
target_include_directories(DoorNroll PRIVATE src)
|
target_include_directories(DoorNroll PRIVATE src)
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
#include "Game.hpp"
|
#include "Game.hpp"
|
||||||
#include "dungeon/DungeonGen.hpp"
|
#include "dungeon/DungeonGen.hpp"
|
||||||
#include "core/SoundManager.hpp"
|
#include "core/SoundManager.hpp"
|
||||||
|
#include "debug/DebugSystem.hpp"
|
||||||
#include <cstdlib>
|
#include <cstdlib>
|
||||||
#include <algorithm>
|
#include <algorithm>
|
||||||
#include <cmath>
|
#include <cmath>
|
||||||
|
|
@ -19,6 +20,7 @@ void Game::init() {
|
||||||
m_renderer.setGraphicsQuality(SoundManager::get().getGraphicsQuality());
|
m_renderer.setGraphicsQuality(SoundManager::get().getGraphicsQuality());
|
||||||
m_state = GameState::MAIN_MENU;
|
m_state = GameState::MAIN_MENU;
|
||||||
SoundManager::get().playTrack(MusicTrack::MENU_HUB);
|
SoundManager::get().playTrack(MusicTrack::MENU_HUB);
|
||||||
|
DebugSystem::get().init(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
void Game::startNewRun() {
|
void Game::startNewRun() {
|
||||||
|
|
@ -137,6 +139,8 @@ void Game::update(float dt) {
|
||||||
// Update dynamic music streaming engine every frame
|
// Update dynamic music streaming engine every frame
|
||||||
SoundManager::get().updateMusic(dt);
|
SoundManager::get().updateMusic(dt);
|
||||||
|
|
||||||
|
DebugSystem::get().update(dt);
|
||||||
|
|
||||||
switch (m_state) {
|
switch (m_state) {
|
||||||
case GameState::MAIN_MENU:
|
case GameState::MAIN_MENU:
|
||||||
updateMainMenu(dt);
|
updateMainMenu(dt);
|
||||||
|
|
@ -1221,7 +1225,12 @@ void Game::draw() {
|
||||||
// Render screen transition wave on top of all 2D/3D elements
|
// Render screen transition wave on top of all 2D/3D elements
|
||||||
m_transition.draw();
|
m_transition.draw();
|
||||||
|
|
||||||
|
// Render debug overlay if enabled
|
||||||
|
DebugSystem::get().drawOverlay();
|
||||||
|
|
||||||
EndDrawing();
|
EndDrawing();
|
||||||
|
|
||||||
|
DebugSystem::get().postDraw();
|
||||||
}
|
}
|
||||||
|
|
||||||
} // namespace dnr
|
} // namespace dnr
|
||||||
|
|
|
||||||
|
|
@ -25,6 +25,9 @@ public:
|
||||||
void draw();
|
void draw();
|
||||||
|
|
||||||
bool shouldClose() const { return m_shouldQuit || WindowShouldClose(); }
|
bool shouldClose() const { return m_shouldQuit || WindowShouldClose(); }
|
||||||
|
void requestQuit() { m_shouldQuit = true; }
|
||||||
|
|
||||||
|
friend class DebugSystem;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
void startNewRun();
|
void startNewRun();
|
||||||
|
|
|
||||||
741
src/debug/DebugSystem.cpp
Normal file
741
src/debug/DebugSystem.cpp
Normal file
|
|
@ -0,0 +1,741 @@
|
||||||
|
#include "DebugSystem.hpp"
|
||||||
|
#include "core/Game.hpp"
|
||||||
|
#include "core/SoundManager.hpp"
|
||||||
|
#include "dungeon/DungeonGen.hpp"
|
||||||
|
#include "combat/Enemy.hpp"
|
||||||
|
|
||||||
|
#include <filesystem>
|
||||||
|
#include <fstream>
|
||||||
|
#include <iostream>
|
||||||
|
#include <chrono>
|
||||||
|
#include <iomanip>
|
||||||
|
#include <sstream>
|
||||||
|
#include <queue>
|
||||||
|
#include <cmath>
|
||||||
|
#include <algorithm>
|
||||||
|
|
||||||
|
namespace dnr {
|
||||||
|
|
||||||
|
DebugSystem& DebugSystem::get() {
|
||||||
|
static DebugSystem instance;
|
||||||
|
return instance;
|
||||||
|
}
|
||||||
|
|
||||||
|
DebugSystem::DebugSystem() = default;
|
||||||
|
DebugSystem::~DebugSystem() = default;
|
||||||
|
|
||||||
|
void DebugSystem::parseArgs(int argc, char* argv[]) {
|
||||||
|
for (int i = 1; i < argc; ++i) {
|
||||||
|
std::string arg = argv[i];
|
||||||
|
|
||||||
|
if (arg == "--bot") {
|
||||||
|
m_config.botEnabled = true;
|
||||||
|
} else if (arg == "--bot-duration" && i + 1 < argc) {
|
||||||
|
m_config.botDuration = std::stof(argv[++i]);
|
||||||
|
m_config.botEnabled = true;
|
||||||
|
} else if (arg == "--bot-max-steps" && i + 1 < argc) {
|
||||||
|
m_config.botMaxSteps = std::stoi(argv[++i]);
|
||||||
|
m_config.botEnabled = true;
|
||||||
|
} else if (arg == "--screenshot" && i + 1 < argc) {
|
||||||
|
m_config.singleScreenshotPath = argv[++i];
|
||||||
|
} else if (arg == "--screenshot-events") {
|
||||||
|
m_config.screenshotEvents = true;
|
||||||
|
} else if (arg == "--screenshot-dir" && i + 1 < argc) {
|
||||||
|
m_config.screenshotDir = argv[++i];
|
||||||
|
} else if (arg == "--state-dump" && i + 1 < argc) {
|
||||||
|
m_config.stateDumpPath = argv[++i];
|
||||||
|
} else if (arg == "--warp-state" && i + 1 < argc) {
|
||||||
|
m_config.warpState = argv[++i];
|
||||||
|
} else if (arg == "--floor" && i + 1 < argc) {
|
||||||
|
m_config.targetFloor = std::stoi(argv[++i]);
|
||||||
|
} else if (arg == "--god") {
|
||||||
|
m_config.godMode = true;
|
||||||
|
} else if (arg == "--turbo") {
|
||||||
|
m_config.turboMode = true;
|
||||||
|
} else if (arg == "--overlay") {
|
||||||
|
m_config.showOverlay = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void DebugSystem::init(Game* game) {
|
||||||
|
m_game = game;
|
||||||
|
|
||||||
|
try {
|
||||||
|
std::filesystem::create_directories(m_config.screenshotDir);
|
||||||
|
} catch (...) {}
|
||||||
|
|
||||||
|
if (m_config.turboMode) {
|
||||||
|
SetTargetFPS(0); // Unlocked framerate for ultra-fast simulation
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void DebugSystem::toggleBot() {
|
||||||
|
m_config.botEnabled = !m_config.botEnabled;
|
||||||
|
SoundManager::get().playTumblerClick();
|
||||||
|
}
|
||||||
|
|
||||||
|
void DebugSystem::toggleGodMode() {
|
||||||
|
m_config.godMode = !m_config.godMode;
|
||||||
|
SoundManager::get().playPickup();
|
||||||
|
}
|
||||||
|
|
||||||
|
void DebugSystem::toggleOverlay() {
|
||||||
|
m_config.showOverlay = !m_config.showOverlay;
|
||||||
|
SoundManager::get().playTumblerClick();
|
||||||
|
}
|
||||||
|
|
||||||
|
void DebugSystem::handleHotkeys() {
|
||||||
|
if (IsKeyPressed(KEY_F1)) {
|
||||||
|
toggleOverlay();
|
||||||
|
}
|
||||||
|
if (IsKeyPressed(KEY_F2)) {
|
||||||
|
toggleGodMode();
|
||||||
|
}
|
||||||
|
if (IsKeyPressed(KEY_F3)) {
|
||||||
|
// Warp to next floor or instant win combat
|
||||||
|
if (m_game) {
|
||||||
|
if (m_game->m_state == GameState::COMBAT) {
|
||||||
|
m_game->m_combat.actionSpinAndShoot(m_game->m_player);
|
||||||
|
if (m_game->m_combat.getEnemy()) {
|
||||||
|
const_cast<Enemy*>(m_game->m_combat.getEnemy())->takeDamage(999, AmmoType::KINETIC);
|
||||||
|
}
|
||||||
|
} else if (m_game->m_state == GameState::EXPLORATION) {
|
||||||
|
m_game->nextFloor();
|
||||||
|
}
|
||||||
|
SoundManager::get().playSkillGreat();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (IsKeyPressed(KEY_F4)) {
|
||||||
|
// Give resources
|
||||||
|
if (m_game) {
|
||||||
|
for (int t = 1; t <= 6; ++t) {
|
||||||
|
m_game->m_player.addAmmo(static_cast<AmmoType>(t), 5);
|
||||||
|
}
|
||||||
|
m_game->m_player.addTechScraps(100);
|
||||||
|
m_game->m_player.addPicks(5);
|
||||||
|
SoundManager::get().playPickup();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (IsKeyPressed(KEY_F10)) {
|
||||||
|
toggleBot();
|
||||||
|
}
|
||||||
|
if (IsKeyPressed(KEY_F12)) {
|
||||||
|
captureScreenshot("");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void DebugSystem::applyWarp() {
|
||||||
|
if (!m_game || m_warpApplied || m_config.warpState.empty()) return;
|
||||||
|
|
||||||
|
m_warpApplied = true;
|
||||||
|
std::string w = m_config.warpState;
|
||||||
|
|
||||||
|
if (w == "HUB") {
|
||||||
|
m_game->startNewRun();
|
||||||
|
} else if (w == "EXPLORATION") {
|
||||||
|
m_game->startNewRun();
|
||||||
|
if (m_config.targetFloor > 1) {
|
||||||
|
m_game->m_dungeon.setFloor(m_config.targetFloor);
|
||||||
|
}
|
||||||
|
m_game->enterCyberspace();
|
||||||
|
} else if (w == "REST_ROOM") {
|
||||||
|
m_game->startNewRun();
|
||||||
|
m_game->m_dungeon.setFloor(m_config.targetFloor);
|
||||||
|
m_game->enterRestRoom();
|
||||||
|
} else if (w == "COMBAT") {
|
||||||
|
m_game->startNewRun();
|
||||||
|
m_game->enterCyberspace();
|
||||||
|
auto enemy = std::make_unique<Enemy>(EnemyType::CYBER_DRONE, 1);
|
||||||
|
m_game->m_combat.startCombat(m_game->m_player, std::move(enemy));
|
||||||
|
m_game->m_state = GameState::COMBAT;
|
||||||
|
} else if (w == "HACKING") {
|
||||||
|
m_game->startNewRun();
|
||||||
|
m_game->enterCyberspace();
|
||||||
|
m_game->m_hacking.start(1);
|
||||||
|
m_game->m_state = GameState::HACKING;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void DebugSystem::update(float dt) {
|
||||||
|
if (!m_game) return;
|
||||||
|
|
||||||
|
m_frameCounter++;
|
||||||
|
|
||||||
|
if (m_frameCounter == 2 && !m_warpApplied) {
|
||||||
|
applyWarp();
|
||||||
|
}
|
||||||
|
|
||||||
|
handleHotkeys();
|
||||||
|
|
||||||
|
// God Mode replenishment
|
||||||
|
if (m_config.godMode) {
|
||||||
|
int maxH = m_game->m_player.getMaxHp();
|
||||||
|
int maxS = m_game->m_player.getMaxShield();
|
||||||
|
m_game->m_player.setStats(maxH, maxH, maxS, maxS, 99);
|
||||||
|
for (int t = 1; t <= 6; ++t) {
|
||||||
|
m_game->m_player.addAmmo(static_cast<AmmoType>(t), 2);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// State change detection for event screenshots
|
||||||
|
if (m_game->m_state != m_lastObservedState) {
|
||||||
|
if (m_config.screenshotEvents) {
|
||||||
|
std::string stateTag = "state_";
|
||||||
|
switch (m_game->m_state) {
|
||||||
|
case GameState::MAIN_MENU: stateTag += "menu"; break;
|
||||||
|
case GameState::HUB: stateTag += "hub"; break;
|
||||||
|
case GameState::EXPLORATION: stateTag += "explore"; break;
|
||||||
|
case GameState::COMBAT: stateTag += "combat"; break;
|
||||||
|
case GameState::HACKING: stateTag += "hacking"; break;
|
||||||
|
case GameState::REST_ROOM: stateTag += "rest"; break;
|
||||||
|
case GameState::VICTORY_LEVEL: stateTag += "victory"; break;
|
||||||
|
case GameState::GAME_OVER: stateTag += "gameover"; break;
|
||||||
|
default: stateTag += "other"; break;
|
||||||
|
}
|
||||||
|
captureEventScreenshot(stateTag);
|
||||||
|
}
|
||||||
|
m_lastObservedState = m_game->m_state;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Bot automation
|
||||||
|
if (m_config.botEnabled) {
|
||||||
|
m_totalBotRunTime += dt;
|
||||||
|
updateBot(dt);
|
||||||
|
|
||||||
|
if (m_config.botDuration > 0.0f && m_totalBotRunTime >= m_config.botDuration) {
|
||||||
|
m_game->m_shouldQuit = true;
|
||||||
|
}
|
||||||
|
if (m_config.botMaxSteps > 0 && m_botStepCount >= m_config.botMaxSteps) {
|
||||||
|
m_game->m_shouldQuit = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Continuous state dump
|
||||||
|
if (!m_config.stateDumpPath.empty() && (m_frameCounter % 15 == 0)) {
|
||||||
|
dumpStateToJson(m_config.stateDumpPath);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void DebugSystem::updateBot(float dt) {
|
||||||
|
if (!m_game || m_game->m_transition.isBusy()) return;
|
||||||
|
|
||||||
|
m_botActionTimer += dt;
|
||||||
|
float actionInterval = m_config.turboMode ? 0.02f : 0.15f;
|
||||||
|
|
||||||
|
if (m_botActionTimer < actionInterval) return;
|
||||||
|
m_botActionTimer = 0.0f;
|
||||||
|
m_botStepCount++;
|
||||||
|
|
||||||
|
switch (m_game->m_state) {
|
||||||
|
case GameState::MAIN_MENU:
|
||||||
|
m_game->startNewRun();
|
||||||
|
break;
|
||||||
|
|
||||||
|
case GameState::HUB:
|
||||||
|
executeBotHubStep();
|
||||||
|
break;
|
||||||
|
|
||||||
|
case GameState::EXPLORATION:
|
||||||
|
executeBotExplorationStep();
|
||||||
|
break;
|
||||||
|
|
||||||
|
case GameState::HACKING:
|
||||||
|
executeBotHackingStep();
|
||||||
|
break;
|
||||||
|
|
||||||
|
case GameState::COMBAT:
|
||||||
|
executeBotCombatStep();
|
||||||
|
break;
|
||||||
|
|
||||||
|
case GameState::REST_ROOM:
|
||||||
|
executeBotRestRoomStep();
|
||||||
|
break;
|
||||||
|
|
||||||
|
case GameState::VICTORY_LEVEL:
|
||||||
|
m_game->enterRestRoom();
|
||||||
|
break;
|
||||||
|
|
||||||
|
case GameState::GAME_OVER:
|
||||||
|
m_game->startNewRun();
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void DebugSystem::executeBotHubStep() {
|
||||||
|
if (!m_game) return;
|
||||||
|
|
||||||
|
if (m_game->m_hubRoom.isFacingPortal(m_game->m_player)) {
|
||||||
|
m_game->tryInteractHub();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
int px = m_game->m_player.getGridX();
|
||||||
|
int py = m_game->m_player.getGridY();
|
||||||
|
Direction facing = m_game->m_player.getFacing();
|
||||||
|
|
||||||
|
// Portal is at (3, 1) facing NORTH
|
||||||
|
if (py > 1) {
|
||||||
|
if (facing != Direction::NORTH) {
|
||||||
|
m_game->m_player.setFacing(Direction::NORTH);
|
||||||
|
} else {
|
||||||
|
m_game->tryMoveHub(0, -1);
|
||||||
|
}
|
||||||
|
} else if (px < 3) {
|
||||||
|
if (facing != Direction::EAST) {
|
||||||
|
m_game->m_player.setFacing(Direction::EAST);
|
||||||
|
} else {
|
||||||
|
m_game->tryMoveHub(1, 0);
|
||||||
|
}
|
||||||
|
} else if (px > 3) {
|
||||||
|
if (facing != Direction::WEST) {
|
||||||
|
m_game->m_player.setFacing(Direction::WEST);
|
||||||
|
} else {
|
||||||
|
m_game->tryMoveHub(-1, 0);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
m_game->m_player.setFacing(Direction::NORTH);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void DebugSystem::executeBotRestRoomStep() {
|
||||||
|
if (!m_game) return;
|
||||||
|
|
||||||
|
if (m_game->m_restRoom.isFacingGateway(m_game->m_player)) {
|
||||||
|
m_game->tryInteractRestRoom();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (m_game->m_restRoom.isFacingRechargeMatrix(m_game->m_player) && !m_game->m_restRoom.isRecharged()) {
|
||||||
|
m_game->tryInteractRestRoom();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
int px = m_game->m_player.getGridX();
|
||||||
|
int py = m_game->m_player.getGridY();
|
||||||
|
|
||||||
|
// Path around fountain: (3, 4) -> (2, 4) -> (2, 1) -> (3, 1) facing NORTH
|
||||||
|
if (px == 3 && py == 4) {
|
||||||
|
m_game->tryMoveRestRoom(-1, 0); // Move to (2, 4)
|
||||||
|
} else if (px == 2 && py > 1) {
|
||||||
|
m_game->tryMoveRestRoom(0, -1); // Move north to (2, 1)
|
||||||
|
} else if (px == 2 && py == 1) {
|
||||||
|
m_game->tryMoveRestRoom(1, 0); // Move east to (3, 1)
|
||||||
|
} else if (px == 3 && py == 1) {
|
||||||
|
m_game->m_player.setFacing(Direction::NORTH);
|
||||||
|
m_game->tryInteractRestRoom();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void DebugSystem::executeBotExplorationStep() {
|
||||||
|
if (!m_game) return;
|
||||||
|
|
||||||
|
// Reset visited map on floor change
|
||||||
|
int curFloor = m_game->m_dungeon.getFloor();
|
||||||
|
if (m_lastDungeonFloor != curFloor) {
|
||||||
|
m_visitedDungeonTiles.clear();
|
||||||
|
m_lastDungeonFloor = curFloor;
|
||||||
|
}
|
||||||
|
|
||||||
|
int px = m_game->m_player.getGridX();
|
||||||
|
int py = m_game->m_player.getGridY();
|
||||||
|
m_visitedDungeonTiles.insert({ px, py });
|
||||||
|
|
||||||
|
// Handle implant / shrine modals
|
||||||
|
if (m_game->m_showImplantDraft) {
|
||||||
|
if (!m_game->m_draftChoices.empty()) {
|
||||||
|
m_game->m_player.addImplant(m_game->m_draftChoices[0]);
|
||||||
|
}
|
||||||
|
m_game->m_showImplantDraft = false;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (m_game->m_showShrineUI) {
|
||||||
|
m_game->m_showShrineUI = false;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// If context prompt is available (door, mine, chest, exit), interact!
|
||||||
|
if (!m_game->m_contextPrompt.empty()) {
|
||||||
|
m_game->tryInteractDungeon();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// BFS Pathfinding to find next actionable or unvisited target
|
||||||
|
int width = m_game->m_dungeon.getWidth();
|
||||||
|
int height = m_game->m_dungeon.getHeight();
|
||||||
|
|
||||||
|
std::vector<std::vector<int>> dist(height, std::vector<int>(width, -1));
|
||||||
|
std::vector<std::vector<std::pair<int, int>>> parent(height, std::vector<std::pair<int, int>>(width, { -1, -1 }));
|
||||||
|
std::queue<std::pair<int, int>> q;
|
||||||
|
|
||||||
|
q.push({ px, py });
|
||||||
|
dist[py][px] = 0;
|
||||||
|
|
||||||
|
int targetX = -1, targetY = -1;
|
||||||
|
bool foundTarget = false;
|
||||||
|
|
||||||
|
int dirs[4][2] = { {0, -1}, {1, 0}, {0, 1}, {-1, 0} };
|
||||||
|
|
||||||
|
while (!q.empty()) {
|
||||||
|
auto [cx, cy] = q.front();
|
||||||
|
q.pop();
|
||||||
|
|
||||||
|
// Priority 1: Unvisited walkable floor or door or exit portal
|
||||||
|
bool isCurrentStart = (cx == px && cy == py);
|
||||||
|
TileType tt = m_game->m_dungeon.getTile(cx, cy);
|
||||||
|
|
||||||
|
if (!isCurrentStart) {
|
||||||
|
if (tt == TileType::EXIT_PORTAL || tt == TileType::LOOT_CACHE ||
|
||||||
|
(tt == TileType::DOOR_LOCKED && m_visitedDungeonTiles.count({cx, cy}) == 0) ||
|
||||||
|
(m_game->m_dungeon.isWalkable(cx, cy) && m_visitedDungeonTiles.count({cx, cy}) == 0)) {
|
||||||
|
targetX = cx;
|
||||||
|
targetY = cy;
|
||||||
|
foundTarget = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (int i = 0; i < 4; ++i) {
|
||||||
|
int nx = cx + dirs[i][0];
|
||||||
|
int ny = cy + dirs[i][1];
|
||||||
|
|
||||||
|
if (nx >= 0 && nx < width && ny >= 0 && ny < height) {
|
||||||
|
if (dist[ny][nx] == -1) {
|
||||||
|
bool canTraverse = m_game->m_dungeon.isWalkable(nx, ny) || m_game->m_dungeon.isDoor(nx, ny);
|
||||||
|
if (canTraverse) {
|
||||||
|
dist[ny][nx] = dist[cy][cx] + 1;
|
||||||
|
parent[ny][nx] = { cx, cy };
|
||||||
|
q.push({ nx, ny });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!foundTarget) {
|
||||||
|
// Fallback: search for exit portal anywhere
|
||||||
|
for (int y = 0; y < height && !foundTarget; ++y) {
|
||||||
|
for (int x = 0; x < width && !foundTarget; ++x) {
|
||||||
|
if (m_game->m_dungeon.getTile(x, y) == TileType::EXIT_PORTAL) {
|
||||||
|
targetX = x;
|
||||||
|
targetY = y;
|
||||||
|
foundTarget = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (foundTarget && (targetX != px || targetY != py)) {
|
||||||
|
// Backtrack to get the immediate next step from (px, py)
|
||||||
|
int currX = targetX;
|
||||||
|
int currY = targetY;
|
||||||
|
while (parent[currY][currX].first != -1 &&
|
||||||
|
(parent[currY][currX].first != px || parent[currY][currX].second != py)) {
|
||||||
|
auto p = parent[currY][currX];
|
||||||
|
currX = p.first;
|
||||||
|
currY = p.second;
|
||||||
|
}
|
||||||
|
|
||||||
|
int stepX = currX;
|
||||||
|
int stepY = currY;
|
||||||
|
|
||||||
|
int dx = stepX - px;
|
||||||
|
int dy = stepY - py;
|
||||||
|
|
||||||
|
Direction desiredDir = Direction::NORTH;
|
||||||
|
if (dx == 1) desiredDir = Direction::EAST;
|
||||||
|
else if (dx == -1) desiredDir = Direction::WEST;
|
||||||
|
else if (dy == 1) desiredDir = Direction::SOUTH;
|
||||||
|
else if (dy == -1) desiredDir = Direction::NORTH;
|
||||||
|
|
||||||
|
if (m_game->m_player.getFacing() == desiredDir) {
|
||||||
|
if (m_game->m_dungeon.isDoorLocked(stepX, stepY)) {
|
||||||
|
m_game->tryInteractDungeon();
|
||||||
|
} else {
|
||||||
|
m_game->tryMoveDungeon(dx, dy);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
m_game->m_player.setFacing(desiredDir);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Random wandering if completely stuck
|
||||||
|
Vector2 dirVec = getDirectionOffset(m_game->m_player.getFacing());
|
||||||
|
int frontX = px + static_cast<int>(dirVec.x);
|
||||||
|
int frontY = py + static_cast<int>(dirVec.y);
|
||||||
|
if (m_game->m_dungeon.isWalkable(frontX, frontY)) {
|
||||||
|
m_game->tryMoveDungeon(static_cast<int>(dirVec.x), static_cast<int>(dirVec.y));
|
||||||
|
} else {
|
||||||
|
m_game->m_player.turnRight();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void DebugSystem::executeBotHackingStep() {
|
||||||
|
if (!m_game) return;
|
||||||
|
|
||||||
|
if (m_game->m_hacking.getPhase() == HackingPhase::PHASE_1_TUMBLER) {
|
||||||
|
auto& tumbler = const_cast<TumblerLock&>(m_game->m_hacking.getTumbler());
|
||||||
|
int pinIdx = tumbler.getSelectedPin();
|
||||||
|
const auto& pin = tumbler.getPin(pinIdx);
|
||||||
|
|
||||||
|
if (pin.isInSweetSpot()) {
|
||||||
|
tumbler.tryLockCurrentPin();
|
||||||
|
if (!tumbler.isAllPinsLocked()) {
|
||||||
|
tumbler.moveRight();
|
||||||
|
}
|
||||||
|
} else if (pin.currentHeight < pin.targetHeight) {
|
||||||
|
tumbler.adjustHeight(0.08f);
|
||||||
|
} else {
|
||||||
|
tumbler.adjustHeight(-0.08f);
|
||||||
|
}
|
||||||
|
} else if (m_game->m_hacking.getPhase() == HackingPhase::PHASE_2_SKILLCHECK) {
|
||||||
|
auto& sc = const_cast<DbdSkillCheck&>(m_game->m_hacking.getSkillCheck());
|
||||||
|
float angle = sc.getNeedleAngle();
|
||||||
|
float gStart = sc.getGreatStart();
|
||||||
|
float gEnd = sc.getGreatEnd();
|
||||||
|
|
||||||
|
if (angle >= gStart && angle <= gEnd) {
|
||||||
|
sc.strike();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void DebugSystem::executeBotCombatStep() {
|
||||||
|
if (!m_game) return;
|
||||||
|
|
||||||
|
CombatPhase phase = m_game->m_combat.getPhase();
|
||||||
|
|
||||||
|
if (phase == CombatPhase::PLAYER_TURN) {
|
||||||
|
int hp = m_game->m_player.getHp();
|
||||||
|
int shield = m_game->m_player.getShield();
|
||||||
|
|
||||||
|
if (hp < 40 && shield < 10) {
|
||||||
|
m_game->m_combat.actionShield(m_game->m_player);
|
||||||
|
} else if (m_game->m_combat.getTripleCooldown() == 0) {
|
||||||
|
m_game->m_combat.actionTripleShot(m_game->m_player);
|
||||||
|
} else {
|
||||||
|
m_game->m_combat.actionSpinAndShoot(m_game->m_player);
|
||||||
|
}
|
||||||
|
} else if (phase == CombatPhase::VICTORY) {
|
||||||
|
bool wasElite = m_game->m_combat.wasElite();
|
||||||
|
m_game->m_player.heal(25);
|
||||||
|
m_game->m_player.addAmmo(AmmoType::KINETIC, 1);
|
||||||
|
m_game->m_player.addAmmo(AmmoType::EMP, 1);
|
||||||
|
m_game->m_state = GameState::EXPLORATION;
|
||||||
|
SoundManager::get().playTrack(MusicTrack::EXPLORATION, true);
|
||||||
|
if (wasElite) {
|
||||||
|
m_game->triggerImplantDraft();
|
||||||
|
}
|
||||||
|
} else if (phase == CombatPhase::DEFEAT) {
|
||||||
|
if (m_config.godMode) {
|
||||||
|
m_game->m_player.heal(100);
|
||||||
|
} else {
|
||||||
|
m_game->m_state = GameState::GAME_OVER;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void DebugSystem::drawOverlay() {
|
||||||
|
if (!m_config.showOverlay || !m_game) return;
|
||||||
|
|
||||||
|
// Strict Zero-Rounding tactical industrial diagnostic HUD
|
||||||
|
int boxX = 16;
|
||||||
|
int boxY = 16;
|
||||||
|
int boxW = 390;
|
||||||
|
int boxH = 220;
|
||||||
|
|
||||||
|
DrawRectangle(boxX, boxY, boxW, boxH, Color{ 6, 10, 16, 235 });
|
||||||
|
DrawRectangleLines(boxX, boxY, boxW, boxH, Color{ 70, 150, 255, 255 }); // Cobalt Blue border
|
||||||
|
DrawRectangle(boxX, boxY, boxW, 24, Color{ 16, 26, 42, 255 });
|
||||||
|
DrawRectangleLines(boxX, boxY, boxW, 24, Color{ 70, 150, 255, 255 });
|
||||||
|
|
||||||
|
DrawText("/// TACTICAL AGENT TELEMETRY ///", boxX + 10, boxY + 6, 12, TacticalColors::PureWhite);
|
||||||
|
|
||||||
|
int textY = boxY + 34;
|
||||||
|
int lineStep = 18;
|
||||||
|
|
||||||
|
std::string stateStr = "STATE: ";
|
||||||
|
switch (m_game->m_state) {
|
||||||
|
case GameState::MAIN_MENU: stateStr += "MAIN_MENU"; break;
|
||||||
|
case GameState::HUB: stateStr += "HUB_ROOM"; break;
|
||||||
|
case GameState::EXPLORATION: stateStr += "EXPLORATION"; break;
|
||||||
|
case GameState::COMBAT: stateStr += "COMBAT"; break;
|
||||||
|
case GameState::HACKING: stateStr += "HACKING_ICE"; break;
|
||||||
|
case GameState::REST_ROOM: stateStr += "REST_ROOM"; break;
|
||||||
|
case GameState::SETTINGS: stateStr += "SETTINGS"; break;
|
||||||
|
case GameState::PAUSE_MENU: stateStr += "PAUSE"; break;
|
||||||
|
case GameState::VICTORY_LEVEL: stateStr += "VICTORY"; break;
|
||||||
|
case GameState::GAME_OVER: stateStr += "GAME_OVER"; break;
|
||||||
|
default: stateStr += "UNKNOWN"; break;
|
||||||
|
}
|
||||||
|
DrawText(stateStr.c_str(), boxX + 12, textY, 13, TacticalColors::PureWhite);
|
||||||
|
textY += lineStep;
|
||||||
|
|
||||||
|
int fps = GetFPS();
|
||||||
|
std::string perfStr = "FPS: " + std::to_string(fps) + " | FRAME: " + std::to_string(m_frameCounter);
|
||||||
|
DrawText(perfStr.c_str(), boxX + 12, textY, 13, Color{ 180, 200, 220, 255 });
|
||||||
|
textY += lineStep;
|
||||||
|
|
||||||
|
const auto& p = m_game->m_player;
|
||||||
|
std::string playerStr = "HP: " + std::to_string(p.getHp()) + "/" + std::to_string(p.getMaxHp()) +
|
||||||
|
" | SHIELD: " + std::to_string(p.getShield()) + "/" + std::to_string(p.getMaxShield()) +
|
||||||
|
" | PICKS: " + std::to_string(p.getCyberPicks());
|
||||||
|
DrawText(playerStr.c_str(), boxX + 12, textY, 13, TacticalColors::PureWhite);
|
||||||
|
textY += lineStep;
|
||||||
|
|
||||||
|
std::string posStr = "GRID: (" + std::to_string(p.getGridX()) + ", " + std::to_string(p.getGridY()) + ") | FLOOR: " +
|
||||||
|
std::to_string(m_game->m_dungeon.getFloor());
|
||||||
|
DrawText(posStr.c_str(), boxX + 12, textY, 13, Color{ 245, 130, 32, 255 }); // Overheat Amber
|
||||||
|
textY += lineStep;
|
||||||
|
|
||||||
|
std::string botStr = m_config.botEnabled ? "[BOT: AUTO-PILOT ON]" : "[BOT: MANUAL]";
|
||||||
|
Color botColor = m_config.botEnabled ? Color{ 95, 185, 75, 255 } : Color{ 140, 150, 160, 255 };
|
||||||
|
DrawText(botStr.c_str(), boxX + 12, textY, 13, botColor);
|
||||||
|
|
||||||
|
std::string godStr = m_config.godMode ? "[GOD: ACTIVE]" : "[GOD: OFF]";
|
||||||
|
Color godColor = m_config.godMode ? Color{ 225, 25, 55, 255 } : Color{ 140, 150, 160, 255 };
|
||||||
|
DrawText(godStr.c_str(), boxX + 210, textY, 13, godColor);
|
||||||
|
textY += lineStep;
|
||||||
|
|
||||||
|
DrawText("F1: HUD | F2: GOD | F3: SKIP | F4: AMMO | F10: BOT | F12: CAPTURE",
|
||||||
|
boxX + 12, textY + 6, 10, Color{ 120, 140, 165, 255 });
|
||||||
|
}
|
||||||
|
|
||||||
|
void DebugSystem::postDraw() {
|
||||||
|
// Single screenshot capture on designated frame
|
||||||
|
if (!m_config.singleScreenshotPath.empty() && !m_singleScreenshotTaken &&
|
||||||
|
m_frameCounter >= m_config.singleScreenshotFrame) {
|
||||||
|
std::filesystem::path p(m_config.singleScreenshotPath);
|
||||||
|
if (p.has_parent_path()) {
|
||||||
|
std::filesystem::create_directories(p.parent_path());
|
||||||
|
}
|
||||||
|
TakeScreenshot(p.make_preferred().string().c_str());
|
||||||
|
m_singleScreenshotTaken = true;
|
||||||
|
if (m_game) {
|
||||||
|
m_game->m_shouldQuit = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Pending event screenshot capture
|
||||||
|
if (!m_pendingScreenshotPath.empty()) {
|
||||||
|
std::filesystem::path p(m_pendingScreenshotPath);
|
||||||
|
if (p.has_parent_path()) {
|
||||||
|
std::filesystem::create_directories(p.parent_path());
|
||||||
|
}
|
||||||
|
TakeScreenshot(p.make_preferred().string().c_str());
|
||||||
|
m_pendingScreenshotPath.clear();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void DebugSystem::captureScreenshot(const std::string& customPath) {
|
||||||
|
if (!customPath.empty()) {
|
||||||
|
m_pendingScreenshotPath = customPath;
|
||||||
|
} else {
|
||||||
|
auto now = std::chrono::system_clock::now();
|
||||||
|
auto timeT = std::chrono::system_clock::to_time_t(now);
|
||||||
|
std::tm tmNow;
|
||||||
|
#if defined(_WIN32)
|
||||||
|
localtime_s(&tmNow, &timeT);
|
||||||
|
#else
|
||||||
|
localtime_r(&timeT, &tmNow);
|
||||||
|
#endif
|
||||||
|
std::ostringstream ss;
|
||||||
|
ss << m_config.screenshotDir << "/shot_"
|
||||||
|
<< std::put_time(&tmNow, "%Y%m%d_%H%M%S") << ".png";
|
||||||
|
m_pendingScreenshotPath = ss.str();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void DebugSystem::captureEventScreenshot(const std::string& eventTag) {
|
||||||
|
auto now = std::chrono::system_clock::now();
|
||||||
|
auto timeT = std::chrono::system_clock::to_time_t(now);
|
||||||
|
std::tm tmNow;
|
||||||
|
#if defined(_WIN32)
|
||||||
|
localtime_s(&tmNow, &timeT);
|
||||||
|
#else
|
||||||
|
localtime_r(&timeT, &tmNow);
|
||||||
|
#endif
|
||||||
|
std::ostringstream ss;
|
||||||
|
ss << m_config.screenshotDir << "/event_"
|
||||||
|
<< std::put_time(&tmNow, "%H%M%S") << "_" << eventTag << ".png";
|
||||||
|
m_pendingScreenshotPath = ss.str();
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string DebugSystem::serializeStateJson() {
|
||||||
|
if (!m_game) return "{}";
|
||||||
|
|
||||||
|
std::ostringstream ss;
|
||||||
|
ss << "{\n";
|
||||||
|
ss << " \"frame\": " << m_frameCounter << ",\n";
|
||||||
|
ss << " \"time\": " << m_totalBotRunTime << ",\n";
|
||||||
|
|
||||||
|
std::string st = "UNKNOWN";
|
||||||
|
switch (m_game->m_state) {
|
||||||
|
case GameState::MAIN_MENU: st = "MAIN_MENU"; break;
|
||||||
|
case GameState::HUB: st = "HUB"; break;
|
||||||
|
case GameState::EXPLORATION: st = "EXPLORATION"; break;
|
||||||
|
case GameState::COMBAT: st = "COMBAT"; break;
|
||||||
|
case GameState::HACKING: st = "HACKING"; break;
|
||||||
|
case GameState::REST_ROOM: st = "REST_ROOM"; break;
|
||||||
|
case GameState::SETTINGS: st = "SETTINGS"; break;
|
||||||
|
case GameState::PAUSE_MENU: st = "PAUSE_MENU"; break;
|
||||||
|
case GameState::VICTORY_LEVEL: st = "VICTORY_LEVEL"; break;
|
||||||
|
case GameState::GAME_OVER: st = "GAME_OVER"; break;
|
||||||
|
default: break;
|
||||||
|
}
|
||||||
|
ss << " \"state\": \"" << st << "\",\n";
|
||||||
|
|
||||||
|
const auto& p = m_game->m_player;
|
||||||
|
ss << " \"player\": {\n";
|
||||||
|
ss << " \"hp\": " << p.getHp() << ",\n";
|
||||||
|
ss << " \"max_hp\": " << p.getMaxHp() << ",\n";
|
||||||
|
ss << " \"shield\": " << p.getShield() << ",\n";
|
||||||
|
ss << " \"max_shield\": " << p.getMaxShield() << ",\n";
|
||||||
|
ss << " \"picks\": " << p.getCyberPicks() << ",\n";
|
||||||
|
ss << " \"scraps\": " << p.getTechScraps() << ",\n";
|
||||||
|
ss << " \"grid_x\": " << p.getGridX() << ",\n";
|
||||||
|
ss << " \"grid_y\": " << p.getGridY() << "\n";
|
||||||
|
ss << " },\n";
|
||||||
|
|
||||||
|
ss << " \"dungeon\": {\n";
|
||||||
|
ss << " \"floor\": " << m_game->m_dungeon.getFloor() << ",\n";
|
||||||
|
ss << " \"visited_tiles\": " << m_visitedDungeonTiles.size() << "\n";
|
||||||
|
ss << " },\n";
|
||||||
|
|
||||||
|
bool inCombat = (m_game->m_state == GameState::COMBAT);
|
||||||
|
ss << " \"combat\": {\n";
|
||||||
|
ss << " \"active\": " << (inCombat ? "true" : "false");
|
||||||
|
if (inCombat && m_game->m_combat.getEnemy()) {
|
||||||
|
const auto* e = m_game->m_combat.getEnemy();
|
||||||
|
ss << ",\n";
|
||||||
|
ss << " \"enemy_name\": \"" << e->getName() << "\",\n";
|
||||||
|
ss << " \"enemy_hp\": " << e->getHp() << ",\n";
|
||||||
|
ss << " \"enemy_max_hp\": " << e->getMaxHp() << ",\n";
|
||||||
|
ss << " \"enemy_shield\": " << e->getShield() << "\n";
|
||||||
|
} else {
|
||||||
|
ss << "\n";
|
||||||
|
}
|
||||||
|
ss << " },\n";
|
||||||
|
|
||||||
|
ss << " \"bot\": {\n";
|
||||||
|
ss << " \"enabled\": " << (m_config.botEnabled ? "true" : "false") << ",\n";
|
||||||
|
ss << " \"steps\": " << m_botStepCount << ",\n";
|
||||||
|
ss << " \"god_mode\": " << (m_config.godMode ? "true" : "false") << "\n";
|
||||||
|
ss << " }\n";
|
||||||
|
|
||||||
|
ss << "}\n";
|
||||||
|
return ss.str();
|
||||||
|
}
|
||||||
|
|
||||||
|
void DebugSystem::dumpStateToJson(const std::string& path) {
|
||||||
|
std::string json = serializeStateJson();
|
||||||
|
std::ofstream out(path, std::ios::trunc);
|
||||||
|
if (out.is_open()) {
|
||||||
|
out << json;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace dnr
|
||||||
91
src/debug/DebugSystem.hpp
Normal file
91
src/debug/DebugSystem.hpp
Normal file
|
|
@ -0,0 +1,91 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "core/Common.hpp"
|
||||||
|
#include <string>
|
||||||
|
#include <vector>
|
||||||
|
#include <set>
|
||||||
|
#include <memory>
|
||||||
|
|
||||||
|
namespace dnr {
|
||||||
|
|
||||||
|
class Game;
|
||||||
|
|
||||||
|
struct DebugConfig {
|
||||||
|
bool botEnabled = false;
|
||||||
|
float botDuration = 0.0f; // Auto-quit after N seconds (0 = infinite)
|
||||||
|
int botMaxSteps = 0; // Step limit (0 = infinite)
|
||||||
|
bool godMode = false;
|
||||||
|
bool turboMode = false;
|
||||||
|
bool showOverlay = false;
|
||||||
|
bool screenshotEvents = false;
|
||||||
|
std::string singleScreenshotPath;
|
||||||
|
int singleScreenshotFrame = 8;
|
||||||
|
std::string screenshotDir = "screenshots";
|
||||||
|
std::string stateDumpPath;
|
||||||
|
std::string warpState; // "HUB", "EXPLORATION", "COMBAT", "REST_ROOM", "HACKING"
|
||||||
|
int targetFloor = 1;
|
||||||
|
};
|
||||||
|
|
||||||
|
class DebugSystem {
|
||||||
|
public:
|
||||||
|
static DebugSystem& get();
|
||||||
|
|
||||||
|
void parseArgs(int argc, char* argv[]);
|
||||||
|
const DebugConfig& getConfig() const { return m_config; }
|
||||||
|
DebugConfig& getConfig() { return m_config; }
|
||||||
|
|
||||||
|
void init(Game* game);
|
||||||
|
void update(float dt);
|
||||||
|
void drawOverlay();
|
||||||
|
void postDraw();
|
||||||
|
|
||||||
|
// Visual captures
|
||||||
|
void captureScreenshot(const std::string& customPath = "");
|
||||||
|
void captureEventScreenshot(const std::string& eventTag);
|
||||||
|
|
||||||
|
// State dump
|
||||||
|
void dumpStateToJson(const std::string& path);
|
||||||
|
|
||||||
|
// Controls
|
||||||
|
void toggleBot();
|
||||||
|
bool isBotEnabled() const { return m_config.botEnabled; }
|
||||||
|
void toggleGodMode();
|
||||||
|
bool isGodMode() const { return m_config.godMode; }
|
||||||
|
void toggleOverlay();
|
||||||
|
bool isOverlayVisible() const { return m_config.showOverlay; }
|
||||||
|
|
||||||
|
void handleHotkeys();
|
||||||
|
void applyWarp();
|
||||||
|
|
||||||
|
private:
|
||||||
|
DebugSystem();
|
||||||
|
~DebugSystem();
|
||||||
|
|
||||||
|
void updateBot(float dt);
|
||||||
|
void executeBotExplorationStep();
|
||||||
|
void executeBotCombatStep();
|
||||||
|
void executeBotHackingStep();
|
||||||
|
void executeBotHubStep();
|
||||||
|
void executeBotRestRoomStep();
|
||||||
|
|
||||||
|
std::string serializeStateJson();
|
||||||
|
|
||||||
|
Game* m_game = nullptr;
|
||||||
|
DebugConfig m_config;
|
||||||
|
|
||||||
|
// Bot telemetry
|
||||||
|
float m_botActionTimer = 0.0f;
|
||||||
|
float m_totalBotRunTime = 0.0f;
|
||||||
|
int m_botStepCount = 0;
|
||||||
|
GameState m_lastObservedState = GameState::MAIN_MENU;
|
||||||
|
std::set<std::pair<int, int>> m_visitedDungeonTiles;
|
||||||
|
int m_lastDungeonFloor = -1;
|
||||||
|
|
||||||
|
// Screenshot & state dump tracking
|
||||||
|
int m_frameCounter = 0;
|
||||||
|
bool m_singleScreenshotTaken = false;
|
||||||
|
bool m_warpApplied = false;
|
||||||
|
std::string m_pendingScreenshotPath;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace dnr
|
||||||
17
src/main.cpp
17
src/main.cpp
|
|
@ -1,14 +1,25 @@
|
||||||
#include "raylib.h"
|
#include "raylib.h"
|
||||||
#include "core/Game.hpp"
|
#include "core/Game.hpp"
|
||||||
|
#include "debug/DebugSystem.hpp"
|
||||||
|
|
||||||
|
int main(int argc, char* argv[]) {
|
||||||
|
dnr::DebugSystem::get().parseArgs(argc, argv);
|
||||||
|
const auto& debugConfig = dnr::DebugSystem::get().getConfig();
|
||||||
|
|
||||||
int main() {
|
|
||||||
const int screenWidth = 1280;
|
const int screenWidth = 1280;
|
||||||
const int screenHeight = 720;
|
const int screenHeight = 720;
|
||||||
|
|
||||||
SetConfigFlags(FLAG_VSYNC_HINT);
|
if (!debugConfig.turboMode) {
|
||||||
|
SetConfigFlags(FLAG_VSYNC_HINT);
|
||||||
|
}
|
||||||
InitWindow(screenWidth, screenHeight, "DoorNroll // Cyber-Revolver Cyberspace Crawler");
|
InitWindow(screenWidth, screenHeight, "DoorNroll // Cyber-Revolver Cyberspace Crawler");
|
||||||
SetExitKey(KEY_NULL); // Prevent ESC from closing the game window
|
SetExitKey(KEY_NULL); // Prevent ESC from closing the game window
|
||||||
SetTargetFPS(60);
|
|
||||||
|
if (debugConfig.turboMode) {
|
||||||
|
SetTargetFPS(0);
|
||||||
|
} else {
|
||||||
|
SetTargetFPS(60);
|
||||||
|
}
|
||||||
|
|
||||||
dnr::Game game;
|
dnr::Game game;
|
||||||
game.init();
|
game.init();
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue