git repos / saland

commit 66f82915

Poul Sander · 2026-01-07 18:54
66f82915f67934737f7b72a5d94d574f9c4fe62b patch · browse files
parent 14d6235b296c3996419fdebd66d61c65204e0109

Add spawn command to spawn monsters

Changed files

M src/ImGuiMainMenu.cpp before
M src/ImGuiOptions.cpp before
M src/ImGuiPlayerSelect.cpp before
M src/ImGuiWorldSelect.cpp before
M src/saland/Game.cpp before
M src/saland/GameConsoleCommand.cpp before
M src/saland/globals.hpp before
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 <format>
#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<float> distX(32.0f, mapWidth - 32.0f);
+ std::uniform_real_distribution<float> distY(32.0f, mapHeight - 32.0f);
+ spawnX = distX(gen);
+ spawnY = distY(gen);
+
+ // Check if tile is not blocking
+ int tileX = static_cast<int>(spawnX / 32);
+ int tileY = static_cast<int>(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> monster = std::dynamic_pointer_cast<Monster>(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<std::string>& 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;