fix(hazard): aterrar damunt d'un Hole salta el LANDING_STUN
This commit is contained in:
1 parent
4da50571eb
commit
3e5217066f
5 files changed
+80
-5
No files matched your search
@@ -1,5 +1,7 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
|
#include <SDL3/SDL.h>
|
||||||
|
|
||||||
#include <cstddef>
|
#include <cstddef>
|
||||||
#include <memory>
|
#include <memory>
|
||||||
#include <string>
|
#include <string>
|
||||||
@@ -74,6 +76,16 @@ class EntityManager {
|
|||||||
// reflecteixi l'estat del frame actual.
|
// reflecteixi l'estat del frame actual.
|
||||||
void checkPlayerCollisions(const std::vector<Player*>& players);
|
void checkPlayerCollisions(const std::vector<Player*>& players);
|
||||||
|
|
||||||
|
// Cerca la primera entitat que (a) sigui decal de sòl
|
||||||
|
// (`is_floor_decal_=true`), (b) admeti triggers d'àrea
|
||||||
|
// (`triggers_on_overlap_=true`), i (c) la seva caixa de col·lisió
|
||||||
|
// solape amb el rect donat. Pensat per a `Player::integrateJump`,
|
||||||
|
// que la consulta a l'aterratge per detectar caigudes sobre Hole
|
||||||
|
// (i en el futur pinchos plans, plaques de pressió, charcos...) i
|
||||||
|
// saltar-se el LANDING_STUN normal. Retorna `nullptr` si no hi ha
|
||||||
|
// cap match — el caller usa el camí d'aterratge habitual.
|
||||||
|
[[nodiscard]] auto findFloorTriggerAt(const SDL_FRect& rect) const -> const Entity*;
|
||||||
|
|
||||||
// Fan-out del hook de pintat: per a cada sarcòfag, cridar
|
// Fan-out del hook de pintat: per a cada sarcòfag, cridar
|
||||||
// notifyTilePainted. Les entitats no-sarcòfag ho ignoren per defecte
|
// notifyTilePainted. Les entitats no-sarcòfag ho ignoren per defecte
|
||||||
// (veure Entity::notifyTilePainted). Invocat com a reacció a
|
// (veure Entity::notifyTilePainted). Invocat com a reacció a
|
||||||
|
|||||||
@@ -58,6 +58,23 @@ void EntityManager::renderAll() {
|
|||||||
for (const auto& e : entities_) { renderEntity(*e); }
|
for (const auto& e : entities_) { renderEntity(*e); }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
auto EntityManager::findFloorTriggerAt(const SDL_FRect& rect) const -> const Entity* {
|
||||||
|
// Cerca lineal sobre `entities_`: cost menyspreable amb poques
|
||||||
|
// desenes d'entitats per sala i una sola crida per frame i per
|
||||||
|
// jugador (a l'aterratge). Si esdevé calent, es podria indexar
|
||||||
|
// per cel·la del tilemap. La doble condició filtra exclusivament
|
||||||
|
// decals de sòl que volen rebre triggers — avui Hole; en el
|
||||||
|
// futur pinchos plans, plaques de pressió, charcos.
|
||||||
|
for (const auto& entity : entities_) {
|
||||||
|
if (!entity->triggersOnOverlap()) { continue; }
|
||||||
|
if (!entity->isFloorDecal()) { continue; }
|
||||||
|
const auto BOX = entity->getCollisionRect();
|
||||||
|
const bool OVERLAP = rect.x + rect.w > BOX.x && BOX.x + BOX.w > rect.x && rect.y + rect.h > BOX.y && BOX.y + BOX.h > rect.y;
|
||||||
|
if (OVERLAP) { return entity.get(); }
|
||||||
|
}
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
void EntityManager::checkPlayerCollisions(const std::vector<Player*>& players) {
|
void EntityManager::checkPlayerCollisions(const std::vector<Player*>& players) {
|
||||||
if (players.empty()) { return; }
|
if (players.empty()) { return; }
|
||||||
for (const auto& entity : entities_) {
|
for (const auto& entity : entities_) {
|
||||||
|
|||||||
@@ -17,6 +17,8 @@
|
|||||||
#include "core/resources/resource_cache.hpp"
|
#include "core/resources/resource_cache.hpp"
|
||||||
#include "core/resources/resource_helper.hpp"
|
#include "core/resources/resource_helper.hpp"
|
||||||
#include "external/fkyaml_node.hpp"
|
#include "external/fkyaml_node.hpp"
|
||||||
|
#include "game/entities/entity.hpp"
|
||||||
|
#include "game/entities/entity_manager.hpp"
|
||||||
#include "game/entities/event_bus.hpp"
|
#include "game/entities/event_bus.hpp"
|
||||||
#include "game/options.hpp"
|
#include "game/options.hpp"
|
||||||
#include "utils/defaults.hpp"
|
#include "utils/defaults.hpp"
|
||||||
@@ -923,6 +925,28 @@ auto Player::integrateJump(float delta_time) -> bool {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
if (jump_z_ <= 0.0F) {
|
if (jump_z_ <= 0.0F) {
|
||||||
|
// Aterratge sobre decal-trigger (Hole, futurs pinchos plans...):
|
||||||
|
// saltem el LANDING_STUN i transicionem directament a la caiguda
|
||||||
|
// pel forat. Sense aquesta detecció, l'aterratge entraria a
|
||||||
|
// LANDING_STUN (~200 ms flotant sobre el forat) i només quan
|
||||||
|
// tornés a GROUNDED `Hole::onPlayerContact` ho dispararia. La
|
||||||
|
// hit-box (8×8 centrada al sprite) és la mateixa que `Hole`
|
||||||
|
// usa al seu re-check; coherent. Si el manager no s'ha
|
||||||
|
// injectat (tests stand-alone), el camí d'aterratge segueix sent
|
||||||
|
// el habitual.
|
||||||
|
if (entity_manager_ != nullptr) {
|
||||||
|
const Entity* trigger = entity_manager_->findFloorTriggerAt(getHitBox());
|
||||||
|
if (trigger != nullptr) {
|
||||||
|
const auto BOX = trigger->getCollisionRect();
|
||||||
|
const float CENTER_X = BOX.x + (BOX.w * 0.5F);
|
||||||
|
const float CENTER_Y = BOX.y + (BOX.h * 0.5F);
|
||||||
|
const float FLOOR_Y = BOX.y + BOX.h;
|
||||||
|
enterHoleFall(CENTER_X, CENTER_Y, FLOOR_Y);
|
||||||
|
// No retornem `true`: no és un aterratge "normal" i
|
||||||
|
// `applyMovement` no ha de cridar `resolveLandingOverlap`.
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
jump_z_ = 0.0F;
|
jump_z_ = 0.0F;
|
||||||
jump_vz_ = 0.0F;
|
jump_vz_ = 0.0F;
|
||||||
state_ = State::LANDING_STUN;
|
state_ = State::LANDING_STUN;
|
||||||
@@ -1455,11 +1479,14 @@ void Player::disable() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void Player::enterHoleFall(float center_x, float center_y, float floor_y) {
|
void Player::enterHoleFall(float center_x, float center_y, float floor_y) {
|
||||||
// Defensa: només transicionem des de GROUNDED. Saltadors, AIRBORNE,
|
// Cap guard de state ací: la responsabilitat la té el caller perquè
|
||||||
// HURT, DYING, etc. no han de caure pel forat (entre altres coses,
|
// hi ha dos callers vàlids amb states distints:
|
||||||
// perquè `checkPlayerCollisions` ja filtra airborne via altura, però
|
// - `Hole::onPlayerContact` (state GROUNDED, el jugador camina i
|
||||||
// afegim el gat ací per coherència amb la resta de transicions).
|
// trepitja el tile del forat).
|
||||||
if (state_ != State::GROUNDED) { return; }
|
// - `Player::integrateJump` (state AIRBORNE, en aterrar damunt
|
||||||
|
// d'un decal-trigger en lloc d'entrar a LANDING_STUN).
|
||||||
|
// Posar `if (state_ != GROUNDED) return` ací bloquejaria el segon
|
||||||
|
// cas i obligaria a hacks tipus "forçar GROUNDED abans de cridar".
|
||||||
|
|
||||||
// Snap del centre de la hit-box a la coordenada central del forat.
|
// Snap del centre de la hit-box a la coordenada central del forat.
|
||||||
// Resolem `pos = center - (hit_offset + hit_size/2)` per a cada eix:
|
// Resolem `pos = center - (hit_offset + hit_size/2)` per a cada eix:
|
||||||
|
|||||||
@@ -12,6 +12,7 @@
|
|||||||
#include "game/map/map.hpp"
|
#include "game/map/map.hpp"
|
||||||
|
|
||||||
class EventBus;
|
class EventBus;
|
||||||
|
class EntityManager;
|
||||||
|
|
||||||
class Player : public AnimatedSprite {
|
class Player : public AnimatedSprite {
|
||||||
public:
|
public:
|
||||||
@@ -345,6 +346,14 @@ class Player : public AnimatedSprite {
|
|||||||
// correspongui (sarcòfags). Si el bus no s'injecta, no emet res.
|
// correspongui (sarcòfags). Si el bus no s'injecta, no emet res.
|
||||||
void setEventBus(EventBus* bus) { bus_ = bus; }
|
void setEventBus(EventBus* bus) { bus_ = bus; }
|
||||||
|
|
||||||
|
// Injecta el manager d'entitats del nivell. L'usa `integrateJump` per
|
||||||
|
// detectar aterratges sobre decals-trigger (forats, plaques de
|
||||||
|
// pressió...) i disparar la transició corresponent en lloc del
|
||||||
|
// LANDING_STUN normal. Propietat externa (mateixa convenció que
|
||||||
|
// `setEventBus`); si no s'injecta, l'aterratge segueix el camí
|
||||||
|
// normal (els tests stand-alone del Player no necessiten injectar-lo).
|
||||||
|
void setEntityManager(EntityManager* manager) { entity_manager_ = manager; }
|
||||||
|
|
||||||
// Tile actual del jugador (centre de `baseBox()` convertit a
|
// Tile actual del jugador (centre de `baseBox()` convertit a
|
||||||
// coordenades de tile del tilemap, usant Map::tile_size i
|
// coordenades de tile del tilemap, usant Map::tile_size i
|
||||||
// Map::tilemap_origin_*). Retorna (col, row).
|
// Map::tilemap_origin_*). Retorna (col, row).
|
||||||
@@ -815,6 +824,12 @@ class Player : public AnimatedSprite {
|
|||||||
// Player només en guarda un punter per publicar events.
|
// Player només en guarda un punter per publicar events.
|
||||||
EventBus* bus_{nullptr};
|
EventBus* bus_{nullptr};
|
||||||
|
|
||||||
|
// Manager d'entitats del nivell (opcional). Propietat externa
|
||||||
|
// (GameScene); l'usa `integrateJump` per consultar
|
||||||
|
// `findFloorTriggerAt` a l'aterratge i decidir si el frame del LAND
|
||||||
|
// ha de disparar `enterHoleFall` en lloc d'un LANDING_STUN normal.
|
||||||
|
EntityManager* entity_manager_{nullptr};
|
||||||
|
|
||||||
// Últim tile publicat. Inicialitzats a un sentinel fora de rang perquè
|
// Últim tile publicat. Inicialitzats a un sentinel fora de rang perquè
|
||||||
// el primer frame en què el bus estigui connectat ja emeti un event.
|
// el primer frame en què el bus estigui connectat ja emeti un event.
|
||||||
int last_tile_col_{-9999};
|
int last_tile_col_{-9999};
|
||||||
|
|||||||
@@ -407,6 +407,10 @@ GameScene::GameScene(Config config)
|
|||||||
// el PaintLayer (subscrit ja) processa el pintat i el fan-out cap als
|
// el PaintLayer (subscrit ja) processa el pintat i el fan-out cap als
|
||||||
// sarcòfags passa per `tile_painted`.
|
// sarcòfags passa per `tile_painted`.
|
||||||
for (auto& p : players_) { p->setEventBus(&entity_manager_.getBus()); }
|
for (auto& p : players_) { p->setEventBus(&entity_manager_.getBus()); }
|
||||||
|
// També injectem el manager perquè `integrateJump` pugui consultar
|
||||||
|
// `findFloorTriggerAt` a l'aterratge (caure dins d'un Hole en lloc
|
||||||
|
// del LANDING_STUN normal).
|
||||||
|
for (auto& p : players_) { p->setEntityManager(&entity_manager_); }
|
||||||
|
|
||||||
// Física del contacte entre jugadors. El factor viu al YAML del mapa
|
// Física del contacte entre jugadors. El factor viu al YAML del mapa
|
||||||
// com a propietat de la piràmide (0.30 per defecte). La vista plana
|
// com a propietat de la piràmide (0.30 per defecte). La vista plana
|
||||||
|
|||||||
Reference in new issue
Block a user