feat(whip): aplicació de dany amb retall de hitbox, alreadyHit i friendly fire
This commit is contained in:
1 parent
d208ba02df
commit
df60709e6d
5 files changed
+217
-6
No files matched your search
@@ -5,7 +5,11 @@
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include "game/entities/concrete/mummy.hpp"
|
||||
#include "game/entities/entity_factory.hpp"
|
||||
#include "game/entities/entity_manager.hpp"
|
||||
#include "game/entities/player.hpp"
|
||||
#include "game/map/map.hpp"
|
||||
|
||||
namespace {
|
||||
|
||||
@@ -92,9 +96,73 @@ void WhipAttack::configure(Direction dir, float spawn_x, float spawn_y, float ra
|
||||
const auto& chosen = spriteFor(dir);
|
||||
setPixelRect({.x = spawn_x, .y = spawn_y, .w = chosen.frame_w, .h = chosen.frame_h});
|
||||
|
||||
computeDamageRect(dir, spawn_x, spawn_y, range_px);
|
||||
|
||||
if (anim_.has_value()) { anim_->reset(); }
|
||||
}
|
||||
|
||||
void WhipAttack::setWorldContext(EntityManager* manager, const std::vector<Player*>* players) {
|
||||
entity_manager_ = manager;
|
||||
players_ = players;
|
||||
}
|
||||
|
||||
void WhipAttack::computeDamageRect(Direction dir, float spawn_x, float spawn_y, float range_px) {
|
||||
// Caminem píxel a píxel des de la vora del jugador en la direcció
|
||||
// d'atac, fins a topar amb un mur o sarc, o esgotar `range_px`.
|
||||
// L'amplada perpendicular és `tile_size` (els passadissos del joc
|
||||
// tenen exactament aquesta mida i l'atac és lateral).
|
||||
const auto TILE = static_cast<float>(Map::tile_size);
|
||||
const int MAX_PX = static_cast<int>(std::max(0.0F, range_px));
|
||||
const auto MAX = static_cast<float>(MAX_PX);
|
||||
float length = 0.0F;
|
||||
|
||||
auto try_probe = [&](float l) -> bool {
|
||||
SDL_FRect probe{0.0F, 0.0F, 0.0F, 0.0F};
|
||||
switch (dir) {
|
||||
case Direction::EAST:
|
||||
probe = {.x = spawn_x, .y = spawn_y, .w = l, .h = TILE};
|
||||
break;
|
||||
case Direction::WEST:
|
||||
// Per a W, la cara "del jugador" és la dreta; com creix
|
||||
// `l`, la vora esquerra retrocedeix. spawn_x apunta al
|
||||
// límit esquerre amb l = MAX (cas no retallat).
|
||||
probe = {.x = spawn_x + (MAX - l), .y = spawn_y, .w = l, .h = TILE};
|
||||
break;
|
||||
case Direction::SOUTH:
|
||||
probe = {.x = spawn_x, .y = spawn_y, .w = TILE, .h = l};
|
||||
break;
|
||||
case Direction::NORTH:
|
||||
probe = {.x = spawn_x, .y = spawn_y + (MAX - l), .w = TILE, .h = l};
|
||||
break;
|
||||
}
|
||||
return Map::insidePlayfield(probe) && !Map::collides(probe);
|
||||
};
|
||||
|
||||
for (int i = 1; i <= MAX_PX; ++i) {
|
||||
const auto L = static_cast<float>(i);
|
||||
if (try_probe(L)) {
|
||||
length = L;
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
switch (dir) {
|
||||
case Direction::EAST:
|
||||
damage_rect_ = {.x = spawn_x, .y = spawn_y, .w = length, .h = TILE};
|
||||
return;
|
||||
case Direction::WEST:
|
||||
damage_rect_ = {.x = spawn_x + (MAX - length), .y = spawn_y, .w = length, .h = TILE};
|
||||
return;
|
||||
case Direction::SOUTH:
|
||||
damage_rect_ = {.x = spawn_x, .y = spawn_y, .w = TILE, .h = length};
|
||||
return;
|
||||
case Direction::NORTH:
|
||||
damage_rect_ = {.x = spawn_x, .y = spawn_y + (MAX - length), .w = TILE, .h = length};
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
auto WhipAttack::getSurfaceKey() const -> const std::string& {
|
||||
return spriteFor(direction_).surface_key;
|
||||
}
|
||||
@@ -115,9 +183,93 @@ auto WhipAttack::spriteFor(Direction dir) const -> const DirSprite& {
|
||||
|
||||
void WhipAttack::update(float delta_time) {
|
||||
Entity::update(delta_time);
|
||||
if (isFrameActive()) { applyDamage(); }
|
||||
if (anim_.has_value() && anim_->completed()) { markDead(); }
|
||||
}
|
||||
|
||||
namespace {
|
||||
|
||||
auto whipDirToMummyDir(WhipAttack::Direction d) -> Mummy::Direction {
|
||||
switch (d) {
|
||||
case WhipAttack::Direction::NORTH:
|
||||
return Mummy::Direction::UP;
|
||||
case WhipAttack::Direction::SOUTH:
|
||||
return Mummy::Direction::DOWN;
|
||||
case WhipAttack::Direction::EAST:
|
||||
return Mummy::Direction::RIGHT;
|
||||
case WhipAttack::Direction::WEST:
|
||||
return Mummy::Direction::LEFT;
|
||||
}
|
||||
return Mummy::Direction::NONE;
|
||||
}
|
||||
|
||||
auto whipDirToPlayerCardinal(WhipAttack::Direction d) -> Player::Cardinal {
|
||||
switch (d) {
|
||||
case WhipAttack::Direction::NORTH:
|
||||
return Player::Cardinal::NORTH;
|
||||
case WhipAttack::Direction::SOUTH:
|
||||
return Player::Cardinal::SOUTH;
|
||||
case WhipAttack::Direction::EAST:
|
||||
return Player::Cardinal::EAST;
|
||||
case WhipAttack::Direction::WEST:
|
||||
return Player::Cardinal::WEST;
|
||||
}
|
||||
return Player::Cardinal::NONE;
|
||||
}
|
||||
|
||||
auto playerIsTargetable(const Player* p) -> bool {
|
||||
if (p == nullptr) { return false; }
|
||||
const auto STATE = p->getState();
|
||||
return STATE != Player::State::HURT && STATE != Player::State::EXITED && STATE != Player::State::DISABLED;
|
||||
}
|
||||
|
||||
// Tracta una mòmia candidata: si és whippable i la seua caja solapa
|
||||
// amb `damage_rect`, la posa a HURT i la marca al set. Extret per
|
||||
// acotar la complexitat cognitiva d'`applyDamage` (clang-tidy llindar).
|
||||
void hitMummyIfApplicable(
|
||||
Entity* e,
|
||||
const SDL_FRect& damage_rect,
|
||||
WhipAttack::Direction dir,
|
||||
std::unordered_set<void*>& already_hit) {
|
||||
if (e == nullptr || e->isDead()) { return; }
|
||||
if (already_hit.contains(static_cast<void*>(e))) { return; }
|
||||
auto* mummy = dynamic_cast<Mummy*>(e);
|
||||
if (mummy == nullptr || !mummy->isWhippable()) { return; }
|
||||
const auto BOX = mummy->getCollisionRect();
|
||||
if (!SDL_HasRectIntersectionFloat(&damage_rect, &BOX)) { return; }
|
||||
mummy->enterHurt(whipDirToMummyDir(dir));
|
||||
already_hit.insert(static_cast<void*>(e));
|
||||
}
|
||||
|
||||
void hitPlayerIfApplicable(
|
||||
Player* p,
|
||||
const Player* owner,
|
||||
const SDL_FRect& damage_rect,
|
||||
WhipAttack::Direction dir,
|
||||
std::unordered_set<void*>& already_hit) {
|
||||
if (p == owner || !playerIsTargetable(p)) { return; }
|
||||
if (already_hit.contains(static_cast<void*>(p))) { return; }
|
||||
const auto BOX = p->getBaseBox();
|
||||
if (!SDL_HasRectIntersectionFloat(&damage_rect, &BOX)) { return; }
|
||||
p->enterHurt(whipDirToPlayerCardinal(dir));
|
||||
already_hit.insert(static_cast<void*>(p));
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
void WhipAttack::applyDamage() {
|
||||
if (entity_manager_ != nullptr) {
|
||||
for (auto* e : entity_manager_->getEntities()) {
|
||||
hitMummyIfApplicable(e, damage_rect_, direction_, already_hit_);
|
||||
}
|
||||
}
|
||||
if (players_ != nullptr) {
|
||||
for (auto* p : *players_) {
|
||||
hitPlayerIfApplicable(p, owner_, damage_rect_, direction_, already_hit_);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
auto WhipAttack::isFrameActive() const -> bool {
|
||||
if (!anim_.has_value()) { return false; }
|
||||
const int FRAME = anim_->currentFrame();
|
||||
|
||||
Reference in new issue
Block a user