diff --git a/src/SagoImGui.cpp b/src/SagoImGui.cpp index f76bf22..df3d404 100644 --- a/src/SagoImGui.cpp +++ b/src/SagoImGui.cpp @@ -29,7 +29,8 @@ void InitImGui(SDL_Window* window, SDL_Renderer* renderer, int width, int height // Setup Dear ImGui context IMGUI_CHECKVERSION(); ImGui::CreateContext(); - ImGuiIO& io = ImGui::GetIO(); (void)io; + ImGuiIO& io = ImGui::GetIO(); + (void)io; io.ConfigFlags |= ImGuiConfigFlags_NavEnableKeyboard; // Enable Keyboard Controls io.ConfigFlags |= ImGuiConfigFlags_NavEnableGamepad; // Enable Gamepad Controls io.DisplaySize.x = static_cast(width); diff --git a/src/editor/SagoTextureSelector.cpp b/src/editor/SagoTextureSelector.cpp index 5cfd8ce..0f0e059 100644 --- a/src/editor/SagoTextureSelector.cpp +++ b/src/editor/SagoTextureSelector.cpp @@ -31,8 +31,7 @@ https://github.com/sago007/saland #include "../global.hpp" -class ChangeSDLColor -{ +class ChangeSDLColor { public: ChangeSDLColor(SDL_Renderer* renderer, Uint8 r, Uint8 g, Uint8 b, Uint8 a) : renderer(renderer) { SDL_GetRenderDrawColor(renderer, &oldr, &oldg, &oldb, &olda); @@ -226,7 +225,7 @@ bool SagoTextureSelector::IsActive() { return isActive; } -void SagoTextureSelector::ProcessInput(const SDL_Event& event, bool &processed) { +void SagoTextureSelector::ProcessInput(const SDL_Event& event, bool& processed) { ImGui_ImplSDL2_ProcessEvent(&event); } diff --git a/src/editor/SagoTextureSelector.hpp b/src/editor/SagoTextureSelector.hpp index dbc3a82..1987b1a 100644 --- a/src/editor/SagoTextureSelector.hpp +++ b/src/editor/SagoTextureSelector.hpp @@ -36,9 +36,9 @@ public: SagoTextureSelector(); SagoTextureSelector(const SagoTextureSelector& orig) = delete; virtual ~SagoTextureSelector(); - + bool IsActive() override; - void ProcessInput(const SDL_Event& event, bool &processed) override; + void ProcessInput(const SDL_Event& event, bool& processed) override; void Draw(SDL_Renderer* target) override; void Update() override; void Init(); diff --git a/src/editor/editor_loader.cpp b/src/editor/editor_loader.cpp index 643c38f..27f3566 100644 --- a/src/editor/editor_loader.cpp +++ b/src/editor/editor_loader.cpp @@ -43,10 +43,10 @@ void from_json(const nlohmann::json& j, SagoSprite& p) { if (j.contains("frame_time")) { j.at("frame_time").get_to(p.frame_time); } - if(j.contains("originx")) { + if (j.contains("originx")) { j.at("originx").get_to(p.originx); } - if(j.contains("originy")) { + if (j.contains("originy")) { j.at("originy").get_to(p.originy); } } diff --git a/src/saland/Game.cpp b/src/saland/Game.cpp index 7863a88..ab4f482 100644 --- a/src/saland/Game.cpp +++ b/src/saland/Game.cpp @@ -443,7 +443,7 @@ void Game::Draw(SDL_Renderer* target) { // Draw black bars where globalData.logicalResize suggests that the physical area ends. // This covers the letterboxing/pillarboxing margins to ensure only the game area is visible. - #if 1 +#if 1 int topMargin = globalData.logicalResize.GetTopMargin(); int leftMargin = globalData.logicalResize.GetLeftMargin(); @@ -468,7 +468,7 @@ void Game::Draw(SDL_Renderer* target) { SDL_SetRenderDrawColor(target, 0, 0, 0, 255); SDL_RenderFillRect(target, &rightBar); } - #endif +#endif if (data->consoleActive && data->console) { data->console->Draw(target); @@ -641,7 +641,12 @@ void Game::Update() { } Monster* monster = dynamic_cast (entity.get()); if (monster) { - UpdateMonster(monster, deltaTime, data->human->X, data->human->Y); + UpdateMonster(monster, deltaTime, data->human.get()); + // Check if monster just attacked + if (monster->attack.animationTime == monster->attack.animationDuration) { + // Attack just started, apply damage to player + MonsterAttackPlayer(monster, data->human.get()); + } } MiscItem* item = dynamic_cast (entity.get()); if (item) { diff --git a/src/saland/GameDraw.cpp b/src/saland/GameDraw.cpp index 4b982ed..1acde25 100644 --- a/src/saland/GameDraw.cpp +++ b/src/saland/GameDraw.cpp @@ -267,6 +267,47 @@ void DrawMonster(SDL_Renderer* target, sago::SagoSpriteHolder* sHolder, const Mo DrawCollision(target, entity, offsetX, offsetY, drawCollision, resize); const sago::SagoSprite& mySprite = sHolder->GetSprite(entity->race + "_" + std::string(1, entity->direction)); mySprite.Draw(target, time, std::round(entity->X) - offsetX, std::round(entity->Y) - offsetY, resize); + + // Draw attack animation if active + if (entity->attack.animationTime > 0.0f) { + // Draw a slash effect in front of the monster + int slashOffsetX = 0; + int slashOffsetY = 0; + + // Position slash based on monster direction + switch (entity->direction) { + case 'N': + slashOffsetY = -20; + break; + case 'S': + slashOffsetY = 20; + break; + case 'E': + slashOffsetX = 20; + break; + case 'W': + slashOffsetX = -20; + break; + } + + int slashX = std::round(entity->X) + slashOffsetX - offsetX; + int slashY = std::round(entity->Y) + slashOffsetY - offsetY; + + // Draw the slash sprite (reuse male_slash animation) + { + // If slash sprite doesn't exist, draw a simple rectangle + SDL_Rect slashRect; + slashRect.x = slashX; + slashRect.y = slashY; + slashRect.w = 16; + slashRect.h = 16; + if (resize) { + resize->LogicalToPhysical(slashRect); + } + SDL_SetRenderDrawColor(target, 255, 100, 100, 200); + SDL_RenderFillRect(target, &slashRect); + } + } } @@ -367,10 +408,10 @@ void UpdateDamageNumbers(Placeable* entity) { // Remove expired damage numbers entity->damageNumbers.erase( - std::remove_if(entity->damageNumbers.begin(), entity->damageNumbers.end(), - [currentTime](const DamageNumber& dmg) { - return (currentTime - dmg.createdAt) >= dmg.duration; - }), - entity->damageNumbers.end() + std::remove_if(entity->damageNumbers.begin(), entity->damageNumbers.end(), + [currentTime](const DamageNumber& dmg) { + return (currentTime - dmg.createdAt) >= dmg.duration; + }), + entity->damageNumbers.end() ); } \ No newline at end of file diff --git a/src/saland/GameItems.cpp b/src/saland/GameItems.cpp index 9da8f7b..656a7d3 100644 --- a/src/saland/GameItems.cpp +++ b/src/saland/GameItems.cpp @@ -53,7 +53,7 @@ static void ReadItemFile(const std::string& filename) { } for (const auto& item : items.GetArray()) { if (item.IsObject()) { - ItemDef new_item = itemDef; + ItemDef new_item = itemDef; for (const auto& member : item.GetObject()) { if (member.name == "itemid") { new_item.itemid = member.value.GetString(); @@ -95,7 +95,7 @@ static void ReadItemFile(const std::string& filename) { static void initItems() { std::vector file_list = sago::GetFileList(item_dir); - for(const std::string& file : file_list) { + for (const std::string& file : file_list) { std::string filename = std::format("{}{}", item_dir, file); printf("Item file: %s\n",filename.c_str()); ReadItemFile(filename); diff --git a/src/saland/GameSpellState.cpp b/src/saland/GameSpellState.cpp index 18cc2d1..654a7c8 100644 --- a/src/saland/GameSpellState.cpp +++ b/src/saland/GameSpellState.cpp @@ -102,16 +102,16 @@ bool GameSpellState::IsSpellSelectActive() { } void GameSpellState::ProcessInput(const SDL_Event& event, bool& processed) { - UpdateMouseCoordinates(event, globalData.mousex, globalData.mousey); - if (event.type == SDL_KEYDOWN) { - if (event.key.keysym.sym == SDLK_ESCAPE) { - data->spellSelectActive = !data->spellSelectActive; - processed = true; - } - } + UpdateMouseCoordinates(event, globalData.mousex, globalData.mousey); + if (event.type == SDL_KEYDOWN) { + if (event.key.keysym.sym == SDLK_ESCAPE) { + data->spellSelectActive = !data->spellSelectActive; + processed = true; + } + } if (!data->spellSelectActive) { return; - } + } if (event.type == SDL_MOUSEBUTTONDOWN) { if (event.button.button == SDL_BUTTON_LEFT) { // Convert physical mouse coordinates to logical coordinates diff --git a/src/saland/GameUpdates.cpp b/src/saland/GameUpdates.cpp index d8947f0..bfbb899 100644 --- a/src/saland/GameUpdates.cpp +++ b/src/saland/GameUpdates.cpp @@ -82,65 +82,102 @@ void UpdateHuman(Human* entity, float fDeltaTime) { entity->Y = place.y*pixel2unit; } -static void MonsterThink(Monster* entity, float playerX, float playerY) { +static void MonsterThink(Monster* entity, Human* player) { + if (!player) { + return; + } + float playerX = player->X; + float playerY = player->Y; + switch (entity->aiState) { - case Monster::State::Roaming: - // Random wandering behavior + case Monster::State::Roaming: + // Random wandering behavior + if (rand()%2==0) { + //1 in 2 chance of changing direction if (rand()%2==0) { - //1 in 2 chance of changing direction - if (rand()%2==0) { - entity->moveX = 0.1f; - } - else { - entity->moveX = -0.1f; - } - if (rand()%2 == 0) { - entity->moveY = 0.1f; - } - else { - entity->moveY = -0.1f; - } + entity->moveX = 0.1f; } - break; - case Monster::State::Aggressive: - // Move towards player - { - float dirX = playerX - entity->X; - float dirY = playerY - entity->Y; - float length = std::sqrt(dirX * dirX + dirY * dirY); - if (length > 0.01f) { - entity->moveX = (dirX / length) * entity->speed; - entity->moveY = (dirY / length) * entity->speed; - } + else { + entity->moveX = -0.1f; } - break; - case Monster::State::Fleeing: - // Move away from player - { - float dirX = entity->X - playerX; - float dirY = entity->Y - playerY; - float length = std::sqrt(dirX * dirX + dirY * dirY); - if (length > 0.01f) { - entity->moveX = (dirX / length) * entity->speed * 1.2f; // Flee slightly faster - entity->moveY = (dirY / length) * entity->speed * 1.2f; - } + if (rand()%2 == 0) { + entity->moveY = 0.1f; } - break; + else { + entity->moveY = -0.1f; + } + } + break; + case Monster::State::Aggressive: + // Move towards player + { + float dirX = playerX - entity->X; + float dirY = playerY - entity->Y; + float length = std::sqrt(dirX * dirX + dirY * dirY); + if (length > 0.01f) { + entity->moveX = (dirX / length) * entity->speed; + entity->moveY = (dirY / length) * entity->speed; + } + } + break; + case Monster::State::Fleeing: + // Move away from player + { + float dirX = entity->X - playerX; + float dirY = entity->Y - playerY; + float length = std::sqrt(dirX * dirX + dirY * dirY); + if (length > 0.01f) { + entity->moveX = (dirX / length) * entity->speed * 1.2f; // Flee slightly faster + entity->moveY = (dirY / length) * entity->speed * 1.2f; + } + } + break; } } -void UpdateMonster(Monster* entity, float fDeltaTime, float playerX, float playerY) { +void UpdateMonster(Monster* entity, float fDeltaTime, Human* player) { SetCreatureMovementEntity(entity, entity->moveX, entity->moveY); b2Vec2 place = entity->body->GetPosition(); entity->X = place.x*pixel2unit; entity->Y = place.y*pixel2unit; - if (entity->aiNextThink > 0.0) { - entity->aiNextThink -= fDeltaTime; + + // Update attack cooldown + if (entity->attack.cooldown > 0.0f) { + entity->attack.cooldown -= fDeltaTime; } - else { - entity->aiNextThink = 2000.0f; - MonsterThink(entity, playerX, playerY); + + // Update attack animation timer + if (entity->attack.animationTime > 0.0f) { + entity->attack.animationTime -= fDeltaTime; } + + // Check if player is alive before attacking + if (player && player->diedAt == 0.0f) { + float playerX = player->X; + float playerY = player->Y; + + // Check if in aggressive state and within attack range + if (entity->aiState == Monster::State::Aggressive && entity->attack.cooldown <= 0.0f) { + float distX = playerX - entity->X; + float distY = playerY - entity->Y; + float distance = std::sqrt(distX * distX + distY * distY); + + if (distance <= entity->attack.range) { + // Attack the player + entity->attack.cooldown = entity->attack.cooldownDuration; + entity->attack.animationTime = entity->attack.animationDuration; + } + } + + if (entity->aiNextThink > 0.0) { + entity->aiNextThink -= fDeltaTime; + } + else { + entity->aiNextThink = 2000.0f; + MonsterThink(entity, player); + } + } + if (entity->health <= 0.0) { entity->removeMe = true; } @@ -175,4 +212,21 @@ void ProjectileHit(Projectile* p, Placeable* target) { // Potentially change monster state to aggressive monster->aiState = Monster::State::Aggressive; } +} + +void MonsterAttackPlayer(Monster* monster, Human* player) { + if (!player || !monster) { + return; + } + + // Apply damage to player + player->health -= monster->attack.damage; + + // Create damage number on player + DamageNumber dmg; + dmg.X = player->X; + dmg.Y = player->Y - player->Radius; + dmg.damage = monster->attack.damage; + dmg.createdAt = SDL_GetTicks(); + player->damageNumbers.push_back(dmg); } \ No newline at end of file diff --git a/src/saland/GameUpdates.hpp b/src/saland/GameUpdates.hpp index 8995f36..5989256 100644 --- a/src/saland/GameUpdates.hpp +++ b/src/saland/GameUpdates.hpp @@ -31,8 +31,9 @@ https://github.com/sago007/saland void ProjectileHit(Projectile* p, Placeable* target); void UpdateHuman(Human* entity, float fDeltaTime); -void UpdateMonster(Monster* entity, float fDeltaTime, float playerX, float playerY); +void UpdateMonster(Monster* entity, float fDeltaTime, Human* player); void UpdateProjectile(Projectile* entity, float fDeltaTime); +void MonsterAttackPlayer(Monster* monster, Human* player); #endif /* GAMEUPDATES_HPP */ diff --git a/src/saland/console/Console.cpp b/src/saland/console/Console.cpp index 0688ecc..1ea53a6 100644 --- a/src/saland/console/Console.cpp +++ b/src/saland/console/Console.cpp @@ -139,7 +139,8 @@ void Console::Draw(SDL_Renderer* target) { if (commands.find(currentInput) != commands.end()) { // Show help message for the complete command ImGui::TextColored(ImVec4(0.5f, 1.0f, 0.5f, 1.0f), "Help: %s", commands[currentInput]->helpMessage().c_str()); - } else { + } + else { // Show matching commands std::vector matches; for (const auto& cmd : commands) { @@ -150,7 +151,9 @@ void Console::Draw(SDL_Renderer* target) { if (matches.size()) { std::string matchText = "Matches: "; for (size_t i = 0; i < matches.size(); ++i) { - if (i > 0) matchText += ", "; + if (i > 0) { + matchText += ", "; + } matchText += matches[i]; } ImGui::TextColored(ImVec4(0.7f, 0.7f, 1.0f, 1.0f), "%s", matchText.c_str()); @@ -219,69 +222,70 @@ int Console::TextEditCallbackStub(ImGuiInputTextCallbackData* data) { int Console::TextEditCallback(ImGuiInputTextCallbackData* data) { switch (data->EventFlag) { - case ImGuiInputTextFlags_CallbackHistory: { - // Clear completion state when using history - completionCandidates.clear(); - completionIndex = -1; - - const int prev_history_pos = historyPos; - if (data->EventKey == ImGuiKey_UpArrow) { - if (historyPos == -1) { - historyPos = commandHistory.size() - 1; - } - else if (historyPos > 0) { - historyPos--; - } + case ImGuiInputTextFlags_CallbackHistory: { + // Clear completion state when using history + completionCandidates.clear(); + completionIndex = -1; + + const int prev_history_pos = historyPos; + if (data->EventKey == ImGuiKey_UpArrow) { + if (historyPos == -1) { + historyPos = commandHistory.size() - 1; } - else if (data->EventKey == ImGuiKey_DownArrow) { - if (historyPos != -1) { - historyPos++; - if (historyPos >= (int)commandHistory.size()) { - historyPos = -1; - } + else if (historyPos > 0) { + historyPos--; + } + } + else if (data->EventKey == ImGuiKey_DownArrow) { + if (historyPos != -1) { + historyPos++; + if (historyPos >= (int)commandHistory.size()) { + historyPos = -1; } } + } - // Apply history - if (prev_history_pos != historyPos) { - const char* history_str = (historyPos >= 0) ? commandHistory[historyPos].c_str() : ""; - data->DeleteChars(0, data->BufTextLen); - data->InsertChars(0, history_str); - data->BufDirty = true; - data->CursorPos = data->BufTextLen; - data->SelectionStart = data->SelectionEnd = data->CursorPos; - } - break; + // Apply history + if (prev_history_pos != historyPos) { + const char* history_str = (historyPos >= 0) ? commandHistory[historyPos].c_str() : ""; + data->DeleteChars(0, data->BufTextLen); + data->InsertChars(0, history_str); + data->BufDirty = true; + data->CursorPos = data->BufTextLen; + data->SelectionStart = data->SelectionEnd = data->CursorPos; } - case ImGuiInputTextFlags_CallbackCompletion: { - // Tab completion - std::string currentInput(data->Buf, data->BufTextLen); - - // Build completion candidates on first tab press - if (completionCandidates.empty()) { - for (const auto& cmd : commands) { - if (cmd.first.find(currentInput) == 0) { - completionCandidates.push_back(cmd.first); - } + break; + } + case ImGuiInputTextFlags_CallbackCompletion: { + // Tab completion + std::string currentInput(data->Buf, data->BufTextLen); + + // Build completion candidates on first tab press + if (completionCandidates.empty()) { + for (const auto& cmd : commands) { + if (cmd.first.find(currentInput) == 0) { + completionCandidates.push_back(cmd.first); } - completionIndex = 0; - } else { - // Cycle through candidates - completionIndex = (completionIndex + 1) % completionCandidates.size(); - } - - if (!completionCandidates.empty()) { - data->DeleteChars(0, data->BufTextLen); - data->InsertChars(0, completionCandidates[completionIndex].c_str()); - data->BufDirty = true; // Tell ImGui the buffer was modified - // Move cursor to end of inserted text - data->CursorPos = data->BufTextLen; - data->SelectionStart = data->SelectionEnd = data->CursorPos; } - break; + completionIndex = 0; } + else { + // Cycle through candidates + completionIndex = (completionIndex + 1) % completionCandidates.size(); + } + + if (!completionCandidates.empty()) { + data->DeleteChars(0, data->BufTextLen); + data->InsertChars(0, completionCandidates[completionIndex].c_str()); + data->BufDirty = true; // Tell ImGui the buffer was modified + // Move cursor to end of inserted text + data->CursorPos = data->BufTextLen; + data->SelectionStart = data->SelectionEnd = data->CursorPos; + } + break; } - + } + return 0; } diff --git a/src/saland/model/placeables.hpp b/src/saland/model/placeables.hpp index b665c1c..52a4239 100644 --- a/src/saland/model/placeables.hpp +++ b/src/saland/model/placeables.hpp @@ -104,6 +104,16 @@ public: float castTime = 400; //Number of milliseconds it will take to complete the cast }; +struct MonsterAttack { + float cooldown = 0.0f; + float cooldownDuration = 1500.0f; // milliseconds between attacks + float range = 40.0f; // pixels + float damage = 5.0f; + std::string animation = "slash"; // Animation to show when attacking + float animationTime = 0.0f; // Time remaining for attack animation + float animationDuration = 300.0f; // milliseconds +}; + class Monster : public Creature { public: enum class State { @@ -118,6 +128,8 @@ public: State aiState = State::Roaming; float targetX = 0.0f; // Used for fleeing direction float targetY = 0.0f; + // Attack logic: + MonsterAttack attack; }; diff --git a/src/saland/model/spells.cpp b/src/saland/model/spells.cpp index 1459939..256f452 100644 --- a/src/saland/model/spells.cpp +++ b/src/saland/model/spells.cpp @@ -103,7 +103,7 @@ void SpellHolder::init() { clearTileSpell.type = SpellCursorType::tile; const char* spellDir = "saland/spells/"; std::vector file_list = sago::GetFileList(spellDir); - for(const std::string& file : file_list) { + for (const std::string& file : file_list) { std::string filename = std::format("{}{}", spellDir, file); printf("Spells: file: %s\n",filename.c_str()); ReadSpellFile(filename);