diff --git a/src/ImGuiMainMenu.cpp b/src/ImGuiMainMenu.cpp index 0bacd10..e637c1c 100644 --- a/src/ImGuiMainMenu.cpp +++ b/src/ImGuiMainMenu.cpp @@ -46,17 +46,17 @@ void ImGuiMainMenu::Draw(SDL_Renderer* target) { ImGuiIO& io = ImGui::GetIO(); ImVec2 window_size(400, 500); ImVec2 window_pos( - (io.DisplaySize.x - window_size.x) * 0.5f, - (io.DisplaySize.y - window_size.y) * 0.5f + (io.DisplaySize.x - window_size.x) * 0.5f, + (io.DisplaySize.y - window_size.y) * 0.5f ); ImGui::SetNextWindowPos(window_pos, ImGuiCond_Always); ImGui::SetNextWindowSize(window_size, ImGuiCond_Always); if (!ImGui::Begin("Saland Adventures", &active, - ImGuiWindowFlags_NoResize | - ImGuiWindowFlags_NoMove | - ImGuiWindowFlags_NoCollapse)) { + ImGuiWindowFlags_NoResize | + ImGuiWindowFlags_NoMove | + ImGuiWindowFlags_NoCollapse)) { ImGui::End(); return; } @@ -119,27 +119,27 @@ void ImGuiMainMenu::ProcessInput(const SDL_Event& event, bool& processed) { void ImGuiMainMenu::ExecutePendingAction() { switch (pendingAction) { - case PendingAction::StartGame: - runStartGame(); - active = false; - break; - case PendingAction::WorldSelect: - runWorldSelect(); - UpdateLabels(); - break; - case PendingAction::PlayerSelect: - runPlayerSelect(); - UpdateLabels(); - break; - case PendingAction::Options: - runMenuOptions(); - break; - case PendingAction::About: - runHelpAbout(); - break; - case PendingAction::None: - // No action to execute - break; + case PendingAction::StartGame: + runStartGame(); + active = false; + break; + case PendingAction::WorldSelect: + runWorldSelect(); + UpdateLabels(); + break; + case PendingAction::PlayerSelect: + runPlayerSelect(); + UpdateLabels(); + break; + case PendingAction::Options: + runMenuOptions(); + break; + case PendingAction::About: + runHelpAbout(); + break; + case PendingAction::None: + // No action to execute + break; } pendingAction = PendingAction::None; } diff --git a/src/ImGuiOptions.cpp b/src/ImGuiOptions.cpp index 050be1c..c336efb 100644 --- a/src/ImGuiOptions.cpp +++ b/src/ImGuiOptions.cpp @@ -38,17 +38,17 @@ void ImGuiOptions::Draw(SDL_Renderer* target) { ImGuiIO& io = ImGui::GetIO(); ImVec2 window_size(400, 250); ImVec2 window_pos( - (io.DisplaySize.x - window_size.x) * 0.5f, - (io.DisplaySize.y - window_size.y) * 0.5f + (io.DisplaySize.x - window_size.x) * 0.5f, + (io.DisplaySize.y - window_size.y) * 0.5f ); ImGui::SetNextWindowPos(window_pos, ImGuiCond_Always); ImGui::SetNextWindowSize(window_size, ImGuiCond_Always); if (!ImGui::Begin("Options", &active, - ImGuiWindowFlags_NoResize | - ImGuiWindowFlags_NoMove | - ImGuiWindowFlags_NoCollapse)) { + ImGuiWindowFlags_NoResize | + ImGuiWindowFlags_NoMove | + ImGuiWindowFlags_NoCollapse)) { ImGui::End(); return; } diff --git a/src/ImGuiPlayerSelect.cpp b/src/ImGuiPlayerSelect.cpp index 55410f2..8fd7470 100644 --- a/src/ImGuiPlayerSelect.cpp +++ b/src/ImGuiPlayerSelect.cpp @@ -76,11 +76,13 @@ void ImGuiPlayerSelect::LoadSelectedPlayer() { nlohmann::json j = nlohmann::json::parse(sago::GetFileContent(filename.c_str())); editingPlayer = j; editingPlayer.save_name = playerNames[selectedPlayerIndex]; - } else { + } + else { editingPlayer = Player(); editingPlayer.save_name = playerNames[selectedPlayerIndex]; } - } else { + } + else { editingPlayer = Player(); editingPlayer.save_name = "player1"; } @@ -117,7 +119,8 @@ void ImGuiPlayerSelect::CreateNewPlayer() { do { newName = std::format("player{}", newPlayerNum); newPlayerNum++; - } while (std::find(playerNames.begin(), playerNames.end(), newName) != playerNames.end()); + } + while (std::find(playerNames.begin(), playerNames.end(), newName) != playerNames.end()); playerNames.push_back(newName); std::sort(playerNames.begin(), playerNames.end()); @@ -162,17 +165,17 @@ void ImGuiPlayerSelect::Draw(SDL_Renderer* target) { ImGuiIO& io = ImGui::GetIO(); ImVec2 window_size(600, 500); ImVec2 window_pos( - (io.DisplaySize.x - window_size.x) * 0.5f, - (io.DisplaySize.y - window_size.y) * 0.5f + (io.DisplaySize.x - window_size.x) * 0.5f, + (io.DisplaySize.y - window_size.y) * 0.5f ); ImGui::SetNextWindowPos(window_pos, ImGuiCond_Always); ImGui::SetNextWindowSize(window_size, ImGuiCond_Always); if (!ImGui::Begin("Player Management", &active, - ImGuiWindowFlags_NoResize | - ImGuiWindowFlags_NoMove | - ImGuiWindowFlags_NoCollapse)) { + ImGuiWindowFlags_NoResize | + ImGuiWindowFlags_NoMove | + ImGuiWindowFlags_NoCollapse)) { ImGui::End(); return; } diff --git a/src/ImGuiWorldSelect.cpp b/src/ImGuiWorldSelect.cpp index 1964121..0d3c477 100644 --- a/src/ImGuiWorldSelect.cpp +++ b/src/ImGuiWorldSelect.cpp @@ -39,17 +39,17 @@ void ImGuiWorldSelect::Draw(SDL_Renderer* target) { ImGuiIO& io = ImGui::GetIO(); ImVec2 window_size(400, 400); ImVec2 window_pos( - (io.DisplaySize.x - window_size.x) * 0.5f, - (io.DisplaySize.y - window_size.y) * 0.5f + (io.DisplaySize.x - window_size.x) * 0.5f, + (io.DisplaySize.y - window_size.y) * 0.5f ); ImGui::SetNextWindowPos(window_pos, ImGuiCond_Always); ImGui::SetNextWindowSize(window_size, ImGuiCond_Always); if (!ImGui::Begin("World Select", &active, - ImGuiWindowFlags_NoResize | - ImGuiWindowFlags_NoMove | - ImGuiWindowFlags_NoCollapse)) { + ImGuiWindowFlags_NoResize | + ImGuiWindowFlags_NoMove | + ImGuiWindowFlags_NoCollapse)) { ImGui::End(); return; } diff --git a/src/saland/Game.cpp b/src/saland/Game.cpp index b4f21ba..4f4f3d1 100644 --- a/src/saland/Game.cpp +++ b/src/saland/Game.cpp @@ -49,6 +49,7 @@ https://github.com/sago007/saland #include "console/Console.hpp" #include "GameConsoleCommand.hpp" #include "GameSpellState.hpp" +#include "GameMonsters.hpp" #include #include "../SagoImGui.hpp" @@ -788,6 +789,69 @@ void Game::Update() { ResetWorld(data->gameRegion.GetRegionX(), data->gameRegion.GetRegionY(), true); reset_region = false; } + // Handle spawn command + if (globalData.pendingSpawnCommand.race.length()) { + MonsterDef def = GetMonsterDefByRace(globalData.pendingSpawnCommand.race); + + static std::random_device rd; + static std::mt19937 gen(rd()); + + for (int i = 0; i < globalData.pendingSpawnCommand.count; ++i) { + float spawnX, spawnY; + + if (globalData.pendingSpawnCommand.spread) { + // Spawn at random locations on the map + // Get map dimensions (in pixels: width*32, height*32) + int mapWidth = data->gameRegion.world.tm.width * 32; + int mapHeight = data->gameRegion.world.tm.height * 32; + + // Try to find a valid spawn location (avoid blocked tiles) + bool validLocation = false; + int attempts = 0; + while (!validLocation && attempts < 20) { + std::uniform_real_distribution distX(32.0f, mapWidth - 32.0f); + std::uniform_real_distribution distY(32.0f, mapHeight - 32.0f); + spawnX = distX(gen); + spawnY = distY(gen); + + // Check if tile is not blocking + int tileX = static_cast(spawnX / 32); + int tileY = static_cast(spawnY / 32); + if (!data->gameRegion.world.tile_blocking(tileX, tileY)) { + validLocation = true; + } + attempts++; + } + + // If no valid location found after attempts, use the last random position anyway + } + else { + // Spawn monsters in a circle around the player + float baseRadius = 80.0f; + float angle = (2.0f * M_PI * i) / globalData.pendingSpawnCommand.count; + spawnX = data->human->X + baseRadius * std::cos(angle); + spawnY = data->human->Y + baseRadius * std::sin(angle); + } + + data->gameRegion.SpawnMonster(def, spawnX, spawnY); + + // Set initial state if specified + auto& spawned = data->gameRegion.placeables.back(); + std::shared_ptr monster = std::dynamic_pointer_cast(spawned); + if (monster) { + if (globalData.pendingSpawnCommand.initialState == "aggressive") { + monster->aiState = Monster::State::Aggressive; + } + else if (globalData.pendingSpawnCommand.initialState == "fleeing") { + monster->aiState = Monster::State::Fleeing; + } + // else remains in default Roaming state + } + } + + // Clear the command + globalData.pendingSpawnCommand = SpawnCommand{}; + } if (data->consoleActive && data->console && !data->console->IsActive()) { data->consoleActive = false; } diff --git a/src/saland/GameConsoleCommand.cpp b/src/saland/GameConsoleCommand.cpp index 4f790b1..9d8b455 100644 --- a/src/saland/GameConsoleCommand.cpp +++ b/src/saland/GameConsoleCommand.cpp @@ -193,11 +193,67 @@ struct DrawOverlayConsoleCommand : public ConsoleCommand { } }; +struct ConsoleCommandSpawn : public ConsoleCommand { + virtual std::string getCommand() const override { + return "spawn"; + } + + virtual std::string run(const std::vector& args) override { + if (args.size() < 3) { + throw std::runtime_error("Must be called like: spawn MONSTER_RACE COUNT [--aggressive|--fleeing]"); + } + + std::string race = args[1]; + int count = 1; + try { + count = std::stoi(args[2]); + } + catch (...) { + std::string error_msg = "Failed to convert \""; + error_msg += args[2] + "\" into an integer"; + throw std::runtime_error(error_msg); + } + + if (count <= 0 || count > 100) { + throw std::runtime_error("Count must be between 1 and 100"); + } + + // Check for optional flags + std::string initialState = "roaming"; + bool spread = false; + for (size_t i = 3; i < args.size(); ++i) { + if (args[i] == "--aggressive") { + initialState = "aggressive"; + } + else if (args[i] == "--fleeing") { + initialState = "fleeing"; + } + else if (args[i] == "--spread") { + spread = true; + } + else { + throw std::runtime_error("Unknown flag: " + args[i] + ". Use --aggressive, --fleeing, or --spread"); + } + } + + // Queue the spawn command (will be processed in Game.cpp) + globalData.pendingSpawnCommand = {race, count, initialState, spread}; + + std::string location = spread ? "randomly across the map" : "around player"; + return "Spawning " + std::to_string(count) + " " + race + "(s) in " + initialState + " state " + location; + } + + virtual std::string helpMessage() const override { + return "Spawn enemies. Usage: spawn MONSTER_RACE COUNT [--aggressive|--fleeing] [--spread]. Example: spawn bee 8 or spawn bat 5 --aggressive --spread"; + } +}; + static ConsoleCommandGiveItem cc_give_item; static ConsoleCommandItem cc_item; static ConsoleCommandQuit cc_quit; static ConsoleCommandConfig cc_config; static DrawOverlayConsoleCommand docc; +static ConsoleCommandSpawn cc_spawn; void GameConsoleCommandRegister() { RegisterCommand(&cc_give_item); @@ -205,4 +261,5 @@ void GameConsoleCommandRegister() { RegisterCommand(&cc_quit); RegisterCommand(&cc_config); RegisterCommand(&docc); + RegisterCommand(&cc_spawn); } \ No newline at end of file diff --git a/src/saland/globals.hpp b/src/saland/globals.hpp index 98ca78e..14e2edf 100644 --- a/src/saland/globals.hpp +++ b/src/saland/globals.hpp @@ -38,6 +38,13 @@ struct PlayerControls { SDL_Keycode block_delete = SDLK_e; }; +struct SpawnCommand { + std::string race; + int count = 0; + std::string initialState; // "roaming", "aggressive", or "fleeing" + bool spread = false; // If true, spawn at random locations instead of around player +}; + struct GlobalData { bool isShuttingDown = false; @@ -61,6 +68,8 @@ struct GlobalData { bool editor = false; sago::SagoLogicalResize logicalResize; + + SpawnCommand pendingSpawnCommand; }; extern GlobalData globalData;