Compare commits
5 Commits
fbfacb825b
...
9345facaed
| Author | SHA1 | Date | |
|---|---|---|---|
| 9345facaed | |||
| 885caa6bc3 | |||
| a77bbe4420 | |||
| 61a4886e62 | |||
| 164f58c883 |
@@ -3,7 +3,7 @@ name: bullet
|
||||
# Shape de la bala. El bounding_radius del .shp dóna el hitbox base (~3 px);
|
||||
# scale el modula visualment i pel hitbox.
|
||||
shape:
|
||||
path: bullet.shp
|
||||
path: bullet/basic.shp
|
||||
scale: 1.0
|
||||
collision_factor: 1.0
|
||||
|
||||
|
||||
@@ -0,0 +1,21 @@
|
||||
name: bullet_double
|
||||
|
||||
# Variant de bala "anular" (dos cercles concèntrics, aspecte d'aura de plasma).
|
||||
# Pensada per a contra-atacs d'enemic (ex: orb dispara una bullet_double al
|
||||
# jugador quan rep un impacte). Mateixa física que la bala bàsica del player;
|
||||
# canvien la forma (cercle doble) i el color per llegir-se com a tret enemic
|
||||
# distintiu (groc verdós vs. el verd laser del player o el roig de bullet_long).
|
||||
shape:
|
||||
path: bullet/double.shp
|
||||
scale: 1.5
|
||||
collision_factor: 1.0
|
||||
|
||||
physics:
|
||||
mass: 0.5
|
||||
restitution: 0.0
|
||||
linear_damping: 0.0
|
||||
angular_damping: 0.0
|
||||
impact_momentum_factor: 4.0
|
||||
|
||||
colors:
|
||||
normal: [200, 255, 80] # groc verdós (chartreuse) — contra-atac de l'orb
|
||||
@@ -4,7 +4,7 @@ name: bullet_long
|
||||
# jugador i amb prou marge per reaccionar. La velocitat NO viu aquí: es passa
|
||||
# a Bullet::fire() i la decideix qui dispara (l'AiTickAction).
|
||||
shape:
|
||||
path: bullet_long.shp
|
||||
path: bullet/long.shp
|
||||
scale: 1.0
|
||||
collision_factor: 0.5
|
||||
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
name: big_pentagon
|
||||
ai_type: big_pentagon # Validat contra el directori; mapeja a EnemyType::BIG_PENTAGON.
|
||||
name: orb
|
||||
ai_type: orb # Validat contra el directori; mapeja a EnemyType::ORB.
|
||||
|
||||
# Shape circular pròpia (anell exterior + anell interior + 6 radis + nucli),
|
||||
# pensada per llegir-se com a "reactor / orb" amb més detall que els enemics
|
||||
# petits.
|
||||
shape:
|
||||
path: enemy_big_orb.shp
|
||||
scale: 1.5
|
||||
path: enemy/orb.shp
|
||||
scale: 1.0
|
||||
collision_factor: 1.0
|
||||
|
||||
physics:
|
||||
@@ -55,7 +55,7 @@ spawn:
|
||||
safety_distance: 54.0 # 1.5× del normal (alineat amb scale 1.5).
|
||||
|
||||
colors:
|
||||
normal: [66, 195, 208] # #42C3D0 — turquesa-cyan distintiu per al boss.
|
||||
normal: [255, 140, 110] # taronja rosat (coral) — distintiu del boss orb.
|
||||
wounded: [255, 220, 60]
|
||||
|
||||
score: 500 # 5x un enemic normal: aguanta 10x més.
|
||||
@@ -69,10 +69,14 @@ health: 10
|
||||
|
||||
events:
|
||||
on_hit:
|
||||
- action: fire_bullet # contra-atac: dispara bullet_double dirigida al jugador
|
||||
bullet: bullet_double
|
||||
bullet_speed: 200.0
|
||||
aim_mode: aimed
|
||||
- action: decrease_health # primer: si arriba a 0 dispara on_no_health
|
||||
#- action: flash # feedback visual de damage parcial
|
||||
- action: create_debris_partial # xip a 0.3x mida (sense ser letal)
|
||||
- action: create_fireworks_small # espurna a cada hit (12 punts, lent)
|
||||
#- action: create_fireworks_small # espurna a cada hit (12 punts, lent)
|
||||
- action: apply_impulse # empenta el cos (skip si will_die)
|
||||
on_no_health:
|
||||
- action: destroy # mort directa, sense wounded
|
||||
@@ -2,7 +2,7 @@ name: pentagon
|
||||
ai_type: pentagon # Validat contra el directori; mapeja a EnemyType::PENTAGON.
|
||||
|
||||
shape:
|
||||
path: enemy_pentagon.shp
|
||||
path: enemy/pentagon.shp
|
||||
scale: 1.0 # multiplicador visual + hitbox sobre la mida nativa del .shp
|
||||
collision_factor: 1.0 # ajust opcional del hitbox (default 1.0)
|
||||
|
||||
|
||||
@@ -2,7 +2,7 @@ name: pinwheel
|
||||
ai_type: pinwheel # Validat contra el directori; mapeja a EnemyType::PINWHEEL.
|
||||
|
||||
shape:
|
||||
path: enemy_pinwheel.shp
|
||||
path: enemy/pinwheel.shp
|
||||
scale: 1.0 # multiplicador visual + hitbox sobre la mida nativa del .shp
|
||||
collision_factor: 1.0 # ajust opcional del hitbox (default 1.0)
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
name: player_ship
|
||||
|
||||
# Shape de la nau. Resolt per ShapeLoader (busca a "shapes/<path>").
|
||||
# Nota: el segon jugador rep un override del shape ("ship2.shp") al ctor.
|
||||
# Nota: el segon jugador rep un override del shape ("ship/wedge.shp") al ctor.
|
||||
# Quan s'introdueixin variants reals de nau, es crearà un YAML separat
|
||||
# per cada model.
|
||||
#
|
||||
@@ -10,7 +10,7 @@ name: player_ship
|
||||
# automàtic de la shape; tocar només si el feel del hitbox
|
||||
# no quadra amb la silueta visual (default 1.0).
|
||||
shape:
|
||||
path: ship.shp
|
||||
path: ship/arrow.shp
|
||||
scale: 1.0
|
||||
collision_factor: 1.0
|
||||
|
||||
|
||||
@@ -2,7 +2,7 @@ name: square
|
||||
ai_type: square # Validat contra el directori; mapeja a EnemyType::SQUARE.
|
||||
|
||||
shape:
|
||||
path: enemy_square.shp
|
||||
path: enemy/square.shp
|
||||
scale: 1.0 # multiplicador visual + hitbox sobre la mida nativa del .shp
|
||||
collision_factor: 1.0 # ajust opcional del hitbox (default 1.0)
|
||||
|
||||
|
||||
@@ -2,7 +2,7 @@ name: star
|
||||
ai_type: star # Validat contra el directori; mapeja a EnemyType::STAR.
|
||||
|
||||
shape:
|
||||
path: star_5.shp
|
||||
path: enemy/star.shp
|
||||
scale: 0.7 # Lleugerament més petit que els altres enemics per diferenciar visualment.
|
||||
collision_factor: 1.0
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# bullet.shp - Projectil (octàgon, radi=3)
|
||||
# bullet/basic.shp - Projectil (octàgon, radi=3)
|
||||
|
||||
name: bullet
|
||||
name: basic
|
||||
scale: 1.0
|
||||
center: 0, 0
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
# bullet_double.shp - Bala anular (dos cercles concèntrics)
|
||||
# bullet/double.shp - Bala anular (dos cercles concèntrics)
|
||||
# © 2026 JailDesigner
|
||||
#
|
||||
# Dos octàgons concèntrics al centre (0,0):
|
||||
@@ -6,7 +6,7 @@
|
||||
# - Interior: radi 2 (lleugerament més petit que la bala estàndard)
|
||||
# Aspecte d'anell / aura de plasma. Bounding radius natiu = 4.
|
||||
|
||||
name: bullet_double
|
||||
name: double
|
||||
scale: 1.0
|
||||
center: 0, 0
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
# bullet_long.shp - Bala allargada vertical (dos mig-octàgons + dos costats)
|
||||
# bullet/long.shp - Bala allargada vertical (dos mig-octàgons + dos costats)
|
||||
# © 2026 JailDesigner
|
||||
#
|
||||
# Càpsula orientada al llarg de l'eix Y: la bala viatja segons el seu angle
|
||||
@@ -15,7 +15,7 @@
|
||||
# Bounding radius natiu = 6 (extrem vertical a y=±6).
|
||||
# collision_factor al YAML compensa el bounding doble (0.5 → hitbox ≈ 3).
|
||||
|
||||
name: bullet_long
|
||||
name: long
|
||||
scale: 1.0
|
||||
center: 0, 0
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
# star.shp - Estrella per a starfield
|
||||
# effect/starfield.shp - Estrella per a starfield
|
||||
# © 2026 JailDesigner
|
||||
|
||||
name: star
|
||||
name: starfield
|
||||
scale: 1.0
|
||||
center: 0, 0
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
# title_flash.shp - Sparkle 4-puntes amb costats còncaus (Atari-style)
|
||||
# effect/title_flash.shp - Sparkle 4-puntes amb costats còncaus (Atari-style)
|
||||
# 4 puntes als cardinals (radi 30) i valls còncaus als 45° (corba Bezier
|
||||
# quadràtica amb control point ±8). 5 punts per arc subdividint la corba.
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
# enemy_big_orb.shp - ORNI enemic gegant (orb circular, doble anell amb radis)
|
||||
# enemy/orb.shp - ORNI enemic gegant (orb circular, doble anell amb radis)
|
||||
# © 2026 JailDesigner
|
||||
#
|
||||
# Forma "reactor / boss circular" — més detall que els enemics petits perquè
|
||||
@@ -9,7 +9,7 @@
|
||||
# - Petit "+" central com a nucli.
|
||||
# Bounding radius natiu = 20 (alineat amb la resta d'enemics).
|
||||
|
||||
name: enemy_big_orb
|
||||
name: orb
|
||||
scale: 1.0
|
||||
center: 0, 0
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# enemy_pentagon.shp - ORNI enemic (pentàgon doble concentric, radi exterior=20)
|
||||
# enemy/pentagon.shp - ORNI enemic (pentàgon doble concentric, radi exterior=20)
|
||||
|
||||
name: enemy_pentagon
|
||||
name: pentagon
|
||||
scale: 1.0
|
||||
center: 0, 0
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
# enemy_pinwheel.shp - ORNI enemic (molinillo de 4 triangles)
|
||||
# enemy/pinwheel.shp - ORNI enemic (molinillo de 4 triangles)
|
||||
# © 2026 JailDesigner
|
||||
|
||||
name: enemy_pinwheel
|
||||
name: pinwheel
|
||||
scale: 1.0
|
||||
center: 0, 0
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# enemy_square.shp - ORNI enemic (rombe, radi=20) + ull amb pupil·la al centre
|
||||
# enemy/square.shp - ORNI enemic (rombe, radi=20) + ull amb pupil·la al centre
|
||||
|
||||
name: enemy_square
|
||||
name: square
|
||||
scale: 1.0
|
||||
center: 0, 0
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
# star_5.shp - ORNI enemic (estrella de 5 puntes, només perímetre)
|
||||
# enemy/star.shp - ORNI enemic (estrella de 5 puntes, només perímetre)
|
||||
# © 2026 JailDesigner
|
||||
#
|
||||
# Pentagrama clàssic: 5 vèrtexs exteriors (radi 20) alternant amb 5 vèrtexs
|
||||
@@ -8,7 +8,7 @@
|
||||
# Sense línies interiors: una única polyline que recorre el perímetre.
|
||||
# Bounding radius natiu ≈ 20 (alineat amb pentagon/square/pinwheel).
|
||||
|
||||
name: star_5
|
||||
name: star
|
||||
scale: 1.0
|
||||
center: 0, 0
|
||||
|
||||
@@ -1,8 +0,0 @@
|
||||
# ship.shp - Nau del jugador 1
|
||||
# Triangle amb base còncava (punta de fletxa)
|
||||
|
||||
name: ship
|
||||
scale: 1.0
|
||||
center: 0, 0
|
||||
|
||||
polyline: 0,-12 8.49,8.49 0,4 -8.49,8.49 0,-12
|
||||
@@ -0,0 +1,7 @@
|
||||
# ship/arrow.shp - Nau del jugador 1 (triangle amb base còncava, punta de fletxa)
|
||||
|
||||
name: arrow
|
||||
scale: 1.0
|
||||
center: 0, 0
|
||||
|
||||
polyline: 0,-12 8.49,8.49 0,4 -8.49,8.49 0,-12
|
||||
@@ -1,7 +1,7 @@
|
||||
# ship2.shp - Nau del jugador 2 (interceptor amb ales)
|
||||
# ship/interceptor.shp - Interceptor amb ales laterals pronunciades
|
||||
# © 2026 JailDesigner
|
||||
|
||||
name: ship2
|
||||
name: interceptor
|
||||
scale: 1.0
|
||||
center: 0, 0
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
# ship2.shp - Nau del jugador 2
|
||||
# Triangle amb cercle central (distintiu visual)
|
||||
# ship/wedge.shp - Nau del jugador 2 (triangle amb cercle central)
|
||||
|
||||
name: ship2
|
||||
name: wedge
|
||||
scale: 1.0
|
||||
center: 0, 0
|
||||
|
||||
+14
-13
@@ -9,7 +9,7 @@
|
||||
# - { timeout: T } → quan han passat T segons des de l'inici de la wave.
|
||||
# - { all_dead: true, timeout: T } → el que arribe abans (amuntegament si vas lent).
|
||||
#
|
||||
# Tipus d'enemic: pentagon, square (alias: cuadrado), pinwheel (alias: molinillo), star, big_pentagon.
|
||||
# Tipus d'enemic: pentagon, square (alias: cuadrado), pinwheel (alias: molinillo), star, orb.
|
||||
|
||||
metadata:
|
||||
version: "2.0"
|
||||
@@ -18,10 +18,11 @@ metadata:
|
||||
|
||||
stages:
|
||||
# STAGE 1 — Tutorial: contacte amb pentagons i un cuadrado.
|
||||
# (Test: també hi ha un orb a la primera onada per provar el contra-atac.)
|
||||
- stage_id: 1
|
||||
multipliers: { velocity: 0.85, rotation: 0.9, tracking: 0.3 }
|
||||
waves:
|
||||
- spawn: [pentagon, pentagon]
|
||||
- spawn: [pentagon, pentagon, orb]
|
||||
spawn_interval: 0.6
|
||||
next: all_dead
|
||||
- spawn: [pentagon, pentagon, square]
|
||||
@@ -47,14 +48,14 @@ stages:
|
||||
spawn_interval: 0.5
|
||||
next: end
|
||||
|
||||
# STAGE 3 — Primer big_pentagon (HP=10).
|
||||
# STAGE 3 — Primer orb (HP=10).
|
||||
- stage_id: 3
|
||||
multipliers: { velocity: 1.0, rotation: 1.0, tracking: 0.5 }
|
||||
waves:
|
||||
- spawn: [pentagon, pentagon, square]
|
||||
spawn_interval: 0.4
|
||||
next: all_dead
|
||||
- spawn: [big_pentagon]
|
||||
- spawn: [orb]
|
||||
next: { all_dead: true, timeout: 12.0 }
|
||||
- spawn: [pinwheel, pinwheel]
|
||||
spawn_interval: 0.5
|
||||
@@ -76,7 +77,7 @@ stages:
|
||||
- spawn: [pinwheel, pinwheel, pinwheel]
|
||||
spawn_interval: 0.4
|
||||
next: all_dead
|
||||
- spawn: [big_pentagon, pentagon, pentagon]
|
||||
- spawn: [orb, pentagon, pentagon]
|
||||
spawn_interval: 0.5
|
||||
next: end
|
||||
|
||||
@@ -93,7 +94,7 @@ stages:
|
||||
- spawn: [pinwheel, pinwheel, star, star]
|
||||
spawn_interval: 0.4
|
||||
next: all_dead
|
||||
- spawn: [big_pentagon, square, square]
|
||||
- spawn: [orb, square, square]
|
||||
spawn_interval: 0.5
|
||||
next: end
|
||||
|
||||
@@ -110,7 +111,7 @@ stages:
|
||||
- spawn: [pinwheel, pinwheel, pinwheel]
|
||||
spawn_interval: 0.3
|
||||
next: all_dead
|
||||
- spawn: [big_pentagon, pinwheel, pinwheel]
|
||||
- spawn: [orb, pinwheel, pinwheel]
|
||||
spawn_interval: 0.4
|
||||
next: end
|
||||
|
||||
@@ -127,7 +128,7 @@ stages:
|
||||
- spawn: [star, star, star]
|
||||
spawn_interval: 0.4
|
||||
next: all_dead
|
||||
- spawn: [big_pentagon, pinwheel, pinwheel, square]
|
||||
- spawn: [orb, pinwheel, pinwheel, square]
|
||||
spawn_interval: 0.5
|
||||
next: end
|
||||
|
||||
@@ -141,7 +142,7 @@ stages:
|
||||
- spawn: [square, square, star, star]
|
||||
spawn_interval: 0.3
|
||||
next: { all_dead: true, timeout: 5.0 }
|
||||
- spawn: [big_pentagon]
|
||||
- spawn: [orb]
|
||||
next: { all_dead: true, timeout: 8.0 }
|
||||
- spawn: [pinwheel, pinwheel, square, star, pentagon]
|
||||
spawn_interval: 0.3
|
||||
@@ -154,13 +155,13 @@ stages:
|
||||
- spawn: [pinwheel, pinwheel, star, star]
|
||||
spawn_interval: 0.3
|
||||
next: { all_dead: true, timeout: 4.0 }
|
||||
- spawn: [big_pentagon, square, square]
|
||||
- spawn: [orb, square, square]
|
||||
spawn_interval: 0.4
|
||||
next: { all_dead: true, timeout: 8.0 }
|
||||
- spawn: [pinwheel, pinwheel, pinwheel, pinwheel]
|
||||
spawn_interval: 0.3
|
||||
next: { all_dead: true, timeout: 5.0 }
|
||||
- spawn: [big_pentagon, pinwheel, pinwheel, square, star]
|
||||
- spawn: [orb, pinwheel, pinwheel, square, star]
|
||||
spawn_interval: 0.4
|
||||
next: end
|
||||
|
||||
@@ -171,12 +172,12 @@ stages:
|
||||
- spawn: [pinwheel, pinwheel, pinwheel, pinwheel]
|
||||
spawn_interval: 0.25
|
||||
next: { all_dead: true, timeout: 4.0 }
|
||||
- spawn: [big_pentagon, square, star]
|
||||
- spawn: [orb, square, star]
|
||||
spawn_interval: 0.4
|
||||
next: { all_dead: true, timeout: 6.0 }
|
||||
- spawn: [pinwheel, pinwheel, star, star, square]
|
||||
spawn_interval: 0.3
|
||||
next: { all_dead: true, timeout: 5.0 }
|
||||
- spawn: [big_pentagon, big_pentagon, pinwheel, pinwheel, star]
|
||||
- spawn: [orb, orb, pinwheel, pinwheel, star]
|
||||
spawn_interval: 0.4
|
||||
next: end
|
||||
|
||||
@@ -20,7 +20,7 @@ namespace Graphics {
|
||||
return it->second; // Cache hit
|
||||
}
|
||||
|
||||
// Normalize path: "ship.shp" → "shapes/ship.shp"
|
||||
// Normalize path: "ship/arrow.shp" → "shapes/ship/arrow.shp"
|
||||
// "logo/letra_j.shp" → "shapes/logo/letra_j.shp"
|
||||
std::string normalized = filename;
|
||||
if (!normalized.starts_with("shapes/")) {
|
||||
|
||||
@@ -19,7 +19,7 @@ namespace Graphics {
|
||||
|
||||
// Carregar shape desde file (con caché)
|
||||
// Retorna punter compartit (nullptr si error)
|
||||
// Exemple: load("ship.shp") → busca a "data/shapes/ship.shp"
|
||||
// Exemple: load("ship/arrow.shp") → busca a "data/shapes/ship/arrow.shp"
|
||||
static auto load(const std::string& filename) -> std::shared_ptr<Shape>;
|
||||
|
||||
// Netejar caché (útil per debug/recàrrega)
|
||||
|
||||
@@ -33,9 +33,9 @@ namespace Effects {
|
||||
|
||||
TrailManager::TrailManager(Rendering::Renderer* renderer)
|
||||
: renderer_(renderer),
|
||||
star_shape_(Graphics::ShapeLoader::load("star.shp")) {
|
||||
star_shape_(Graphics::ShapeLoader::load("effect/starfield.shp")) {
|
||||
if (!star_shape_ || !star_shape_->isValid()) {
|
||||
std::cerr << "[TrailManager] Warning: no s'ha pogut load star.shp\n";
|
||||
std::cerr << "[TrailManager] Warning: no s'ha pogut load effect/starfield.shp\n";
|
||||
}
|
||||
for (auto& particle : pool_) {
|
||||
particle.active = false;
|
||||
|
||||
@@ -114,6 +114,6 @@ void Bullet::desactivar() {
|
||||
|
||||
void Bullet::draw() const {
|
||||
if (is_active_ && shape_) {
|
||||
Rendering::renderShape(renderer_, shape_, center_, angle_, 1.0F, 1.0F, brightness_, config_->colors.normal);
|
||||
Rendering::renderShape(renderer_, shape_, center_, angle_, config_->shape.scale, 1.0F, brightness_, config_->colors.normal);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -21,7 +21,7 @@ enum class EnemyType : uint8_t {
|
||||
SQUARE = 1, // Square perseguidor (tracks ship)
|
||||
PINWHEEL = 2, // Molinillo agresivo (rápido, girando)
|
||||
STAR = 3, // Estrella de 5 puntes (clone visual de Pentagon, comportament zigzag)
|
||||
BIG_PENTAGON = 4, // Pentàgon gegant tough (HP=10, chase lent — primer enemic HP>1)
|
||||
ORB = 4, // Orb gegant tough (HP=10, chase lent — primer enemic HP>1)
|
||||
};
|
||||
|
||||
// Forward declaration — EnemyConfig viu a enemy_config.hpp i s'inclou només a enemy.cpp.
|
||||
|
||||
@@ -30,7 +30,7 @@ namespace {
|
||||
if (s == "square") { return EnemyType::SQUARE; }
|
||||
if (s == "pinwheel") { return EnemyType::PINWHEEL; }
|
||||
if (s == "star") { return EnemyType::STAR; }
|
||||
if (s == "big_pentagon") { return EnemyType::BIG_PENTAGON; }
|
||||
if (s == "orb") { return EnemyType::ORB; }
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
@@ -186,6 +186,11 @@ namespace {
|
||||
}
|
||||
}
|
||||
|
||||
// Forward-decl: aimModeFromString viu més avall (junt amb la resta de
|
||||
// helpers d'AI) però parseActionList el necessita per al payload de
|
||||
// FIRE_BULLET. Evita reordenar tot el bloc.
|
||||
auto aimModeFromString(const std::string& s) -> std::optional<AimMode>;
|
||||
|
||||
auto actionTypeFromString(const std::string& s) -> std::optional<EnemyActionType> {
|
||||
if (s == "set_hurt") { return EnemyActionType::SET_HURT; }
|
||||
if (s == "destroy") { return EnemyActionType::DESTROY; }
|
||||
@@ -197,6 +202,7 @@ namespace {
|
||||
if (s == "apply_impulse") { return EnemyActionType::APPLY_IMPULSE; }
|
||||
if (s == "decrease_health") { return EnemyActionType::DECREASE_HEALTH; }
|
||||
if (s == "flash") { return EnemyActionType::FLASH; }
|
||||
if (s == "fire_bullet") { return EnemyActionType::FIRE_BULLET; }
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
@@ -219,19 +225,50 @@ namespace {
|
||||
<< event_name << " (" << enemy_name << ")\n";
|
||||
return false;
|
||||
}
|
||||
out.push_back({*PARSED});
|
||||
EnemyAction action;
|
||||
action.type = *PARSED;
|
||||
// Payload de FIRE_BULLET. Camps opcionals; els defaults són els del struct.
|
||||
if (action.type == EnemyActionType::FIRE_BULLET) {
|
||||
if (item.contains("bullet")) {
|
||||
action.bullet_config_name = item["bullet"].get_value<std::string>();
|
||||
}
|
||||
if (item.contains("bullet_speed")) {
|
||||
action.bullet_speed = item["bullet_speed"].get_value<float>();
|
||||
}
|
||||
if (item.contains("aim_mode")) {
|
||||
const auto AIM_STR = item["aim_mode"].get_value<std::string>();
|
||||
const auto AIM = aimModeFromString(AIM_STR);
|
||||
if (!AIM) {
|
||||
std::cerr << "[EnemyConfig] Error: aim_mode desconegut '" << AIM_STR
|
||||
<< "' a " << event_name << " (" << enemy_name << ")\n";
|
||||
return false;
|
||||
}
|
||||
action.aim_mode = *AIM;
|
||||
}
|
||||
if (item.contains("jitter_rad")) {
|
||||
action.jitter_rad = item["jitter_rad"].get_value<float>();
|
||||
}
|
||||
}
|
||||
out.push_back(action);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
// Defaults: replica el flux hardcoded actual (set_hurt → destroy → score+debris+fireworks).
|
||||
// Construïm via mutació per esquivar warnings de designated-init parcial sobre
|
||||
// l'EnemyAction (que té payload de FIRE_BULLET no rellevant ací).
|
||||
void fillLegacyDefaults(EnemyEventConfig& events) {
|
||||
events.on_hit = {{EnemyActionType::SET_HURT}};
|
||||
events.on_hurt_end = {{EnemyActionType::DESTROY}};
|
||||
const auto MAKE = [](EnemyActionType type) {
|
||||
EnemyAction a;
|
||||
a.type = type;
|
||||
return a;
|
||||
};
|
||||
events.on_hit = {MAKE(EnemyActionType::SET_HURT)};
|
||||
events.on_hurt_end = {MAKE(EnemyActionType::DESTROY)};
|
||||
events.on_destroy = {
|
||||
{EnemyActionType::ADD_SCORE},
|
||||
{EnemyActionType::CREATE_DEBRIS},
|
||||
{EnemyActionType::CREATE_FIREWORKS},
|
||||
MAKE(EnemyActionType::ADD_SCORE),
|
||||
MAKE(EnemyActionType::CREATE_DEBRIS),
|
||||
MAKE(EnemyActionType::CREATE_FIREWORKS),
|
||||
};
|
||||
}
|
||||
|
||||
@@ -353,8 +390,8 @@ namespace {
|
||||
out.movement.rotation_proximity_multiplier = legacy.rotation_proximity_multiplier;
|
||||
out.movement.proximity_distance = legacy.proximity_distance;
|
||||
break;
|
||||
case EnemyType::BIG_PENTAGON:
|
||||
// Sense legacy fallback: el YAML del big_pentagon ha de definir
|
||||
case EnemyType::ORB:
|
||||
// Sense legacy fallback: el YAML de l'orb ha de definir
|
||||
// ai.movement explícitament. Default chase lent perquè el switch
|
||||
// siga exhaustiu i no falli si algú omet el bloc ai.
|
||||
out.movement.type = MovementType::CHASE;
|
||||
|
||||
@@ -8,8 +8,11 @@
|
||||
#pragma once
|
||||
|
||||
#include <cstdint>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include "game/entities/enemy_ai.hpp" // AimMode
|
||||
|
||||
enum class EnemyEventType : uint8_t {
|
||||
ON_HIT, // Impactat per una bala
|
||||
ON_NO_HEALTH, // health ha arribat a 0 o menys aquest frame (via DECREASE_HEALTH)
|
||||
@@ -28,10 +31,18 @@ enum class EnemyActionType : uint8_t {
|
||||
APPLY_IMPULSE, // Aplica l'impuls de la bala impactant
|
||||
DECREASE_HEALTH, // Decrementa health_; si <=0, dispatcha ON_NO_HEALTH
|
||||
FLASH, // Flash visual breu (feedback per impacte parcial)
|
||||
FIRE_BULLET, // Dispara una bala (config per nom) dirigida o aleatòria
|
||||
};
|
||||
|
||||
struct EnemyAction {
|
||||
EnemyActionType type;
|
||||
|
||||
// Payload de FIRE_BULLET (ignorat per altres tipus). Paral·lel a AiTickAction
|
||||
// perquè un futur refactor pugui compartir doShoot/doFireBullet si val la pena.
|
||||
std::string bullet_config_name;
|
||||
float bullet_speed{200.0F};
|
||||
AimMode aim_mode{AimMode::RANDOM};
|
||||
float jitter_rad{0.0F};
|
||||
};
|
||||
|
||||
struct EnemyEventConfig {
|
||||
|
||||
@@ -13,7 +13,7 @@ EnemyConfig EnemyRegistry::pentagon_config;
|
||||
EnemyConfig EnemyRegistry::square_config;
|
||||
EnemyConfig EnemyRegistry::pinwheel_config;
|
||||
EnemyConfig EnemyRegistry::star_config;
|
||||
EnemyConfig EnemyRegistry::big_pentagon_config;
|
||||
EnemyConfig EnemyRegistry::orb_config;
|
||||
bool EnemyRegistry::loaded = false;
|
||||
|
||||
namespace {
|
||||
@@ -40,7 +40,7 @@ auto EnemyRegistry::loadAll() -> bool {
|
||||
loadOne("square", EnemyType::SQUARE, square_config) &&
|
||||
loadOne("pinwheel", EnemyType::PINWHEEL, pinwheel_config) &&
|
||||
loadOne("star", EnemyType::STAR, star_config) &&
|
||||
loadOne("big_pentagon", EnemyType::BIG_PENTAGON, big_pentagon_config);
|
||||
loadOne("orb", EnemyType::ORB, orb_config);
|
||||
loaded = OK;
|
||||
if (OK) {
|
||||
std::cout << "[EnemyRegistry] 5 configuracions d'enemic carregades.\n";
|
||||
@@ -62,8 +62,8 @@ auto EnemyRegistry::get(EnemyType type) -> const EnemyConfig& {
|
||||
return pinwheel_config;
|
||||
case EnemyType::STAR:
|
||||
return star_config;
|
||||
case EnemyType::BIG_PENTAGON:
|
||||
return big_pentagon_config;
|
||||
case EnemyType::ORB:
|
||||
return orb_config;
|
||||
}
|
||||
std::cerr << "[EnemyRegistry] FATAL: tipus desconegut\n";
|
||||
std::exit(EXIT_FAILURE);
|
||||
|
||||
@@ -27,6 +27,6 @@ class EnemyRegistry {
|
||||
static EnemyConfig square_config;
|
||||
static EnemyConfig pinwheel_config;
|
||||
static EnemyConfig star_config;
|
||||
static EnemyConfig big_pentagon_config;
|
||||
static EnemyConfig orb_config;
|
||||
static bool loaded;
|
||||
};
|
||||
|
||||
@@ -26,7 +26,7 @@ Ship::Ship(Rendering::Renderer* renderer, PlayerConfig config, const char* shape
|
||||
config_(std::move(config)) {
|
||||
brightness_ = Defaults::Brightness::NAU;
|
||||
|
||||
// El shape pot venir del YAML o ser overridden (ex: P2 amb "ship2.shp").
|
||||
// El shape pot venir del YAML o ser overridden (ex: P2 amb "ship/wedge.shp").
|
||||
const std::string SHAPE_PATH = (shape_override != nullptr) ? shape_override : config_.shape.path;
|
||||
shape_ = Graphics::ShapeLoader::load(SHAPE_PATH);
|
||||
if (!shape_ || !shape_->isValid()) {
|
||||
|
||||
@@ -83,7 +83,7 @@ GameScene::GameScene(SDLManager& sdl, SceneContext& context)
|
||||
|
||||
// Inicialitzar naves: P1 amb el shape del YAML, P2 amb override visual.
|
||||
ships_[0] = Ship(sdl.getRenderer(), *player_config); // Jugador 1: nau estàndard
|
||||
ships_[1] = Ship(sdl.getRenderer(), *player_config, "ship2.shp"); // Jugador 2: interceptor amb ales
|
||||
ships_[1] = Ship(sdl.getRenderer(), *player_config, "ship/wedge.shp"); // Jugador 2: triangle amb cercle central
|
||||
|
||||
// Inicialitzar balas con renderer
|
||||
std::ranges::fill(bullets_, Bullet(sdl.getRenderer()));
|
||||
|
||||
@@ -78,7 +78,7 @@ TitleScene::TitleScene(SDLManager& sdl, SceneContext& context)
|
||||
|
||||
// Flash que tapa el "pop" final de la nau al VP. Es spawneja al centre
|
||||
// de pantalla (= projecció del VP) quan ship_animator avisa.
|
||||
flash_shape_ = Graphics::ShapeLoader::load("title_flash.shp");
|
||||
flash_shape_ = Graphics::ShapeLoader::load("effect/title_flash.shp");
|
||||
ship_animator_->setOnShipDisappear([this](int /*player_id*/) {
|
||||
triggerFlash(Vec2{
|
||||
.x = static_cast<float>(Defaults::Window::WIDTH) / 2.0F,
|
||||
|
||||
@@ -219,8 +219,8 @@ namespace StageSystem {
|
||||
out = EnemyType::PINWHEEL;
|
||||
} else if (type_str == "star") {
|
||||
out = EnemyType::STAR;
|
||||
} else if (type_str == "big_pentagon") {
|
||||
out = EnemyType::BIG_PENTAGON;
|
||||
} else if (type_str == "orb") {
|
||||
out = EnemyType::ORB;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -234,7 +234,7 @@ namespace Systems::Collision {
|
||||
processWoundedDeaths(ctx); // expiran ANTES de ser tocadas por bala este frame
|
||||
detectBulletEnemy(ctx);
|
||||
// Wounded chain desactivat: era massa fàcil que un enemic ferit topés
|
||||
// amb el big_pentagon (10 HP) i el matés instantàniament. La regla
|
||||
// amb l'orb (10 HP) i el matés instantàniament. La regla
|
||||
// "ferit-toca-sa → ferit" queda permanentment fora.
|
||||
detectShipEnemy(ctx);
|
||||
detectBulletPlayer(ctx);
|
||||
|
||||
@@ -3,13 +3,19 @@
|
||||
|
||||
#include "game/systems/enemy_event_dispatcher.hpp"
|
||||
|
||||
#include <cmath>
|
||||
#include <cstdint>
|
||||
#include <cstdlib>
|
||||
|
||||
#include "core/defaults.hpp"
|
||||
#include "core/types.hpp"
|
||||
#include "game/constants.hpp"
|
||||
#include "game/entities/bullet.hpp"
|
||||
#include "game/entities/bullet_config.hpp"
|
||||
#include "game/entities/bullet_registry.hpp"
|
||||
#include "game/entities/enemy_ai.hpp"
|
||||
#include "game/entities/enemy_config.hpp"
|
||||
#include "game/entities/ship.hpp"
|
||||
|
||||
namespace Systems::EnemyEvents {
|
||||
|
||||
@@ -72,6 +78,87 @@ namespace Systems::EnemyEvents {
|
||||
(bullet->getBody().mass * bullet->getConfig().physics.impact_momentum_factor);
|
||||
enemy.applyImpulse(IMPULSE);
|
||||
}
|
||||
|
||||
auto randFloat01() -> float {
|
||||
return static_cast<float>(std::rand()) / static_cast<float>(RAND_MAX);
|
||||
}
|
||||
|
||||
// Còpia local de la mateixa primitiva que viu a enemy_ai_system.cpp.
|
||||
// No s'ha extret a un header compartit perquè és l'únic punt de
|
||||
// duplicació; si apareix un tercer consumidor, refactoritzar.
|
||||
auto findNearestShipPosition(const Enemy& enemy) -> const Vec2* {
|
||||
const Vec2& self = enemy.getCenter();
|
||||
const Vec2* best = nullptr;
|
||||
float best_dist_sq = 0.0F;
|
||||
for (const Ship* ship : enemy.getShips()) {
|
||||
if (ship == nullptr || !ship->isActive()) {
|
||||
continue;
|
||||
}
|
||||
const Vec2& pos = ship->getCenter();
|
||||
const Vec2 DELTA = pos - self;
|
||||
const float DIST_SQ = DELTA.lengthSquared();
|
||||
if (best == nullptr || DIST_SQ < best_dist_sq) {
|
||||
best = &pos;
|
||||
best_dist_sq = DIST_SQ;
|
||||
}
|
||||
}
|
||||
return best;
|
||||
}
|
||||
|
||||
// FIRE_BULLET: paral·lel a doShoot() d'enemy_ai_system.cpp, però disparat
|
||||
// per esdeveniment (típicament on_hit per a contra-atacs) en lloc de
|
||||
// periòdicament. owner_id es deriva de l'índex dins ctx.enemies via
|
||||
// aritmètica de punters (l'array és contigu).
|
||||
void doFireBullet(Systems::Collision::Context& ctx, const Enemy& enemy, const EnemyAction& action) {
|
||||
if (action.bullet_config_name.empty()) {
|
||||
return;
|
||||
}
|
||||
const BulletConfig* cfg = BulletRegistry::get(action.bullet_config_name);
|
||||
if (cfg == nullptr) {
|
||||
return;
|
||||
}
|
||||
Bullet* slot = nullptr;
|
||||
constexpr std::size_t START = Defaults::Entities::ENEMY_BULLET_START_IDX;
|
||||
constexpr std::size_t END = START + Defaults::Entities::MAX_ENEMY_BULLETS;
|
||||
for (std::size_t i = START; i < END; ++i) {
|
||||
if (!ctx.bullets[i].isActive()) {
|
||||
slot = &ctx.bullets[i];
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (slot == nullptr) {
|
||||
return; // pool d'enemic ple
|
||||
}
|
||||
|
||||
float angle = 0.0F;
|
||||
if (action.aim_mode == AimMode::AIMED) {
|
||||
const Vec2* target = findNearestShipPosition(enemy);
|
||||
if (target == nullptr) {
|
||||
angle = randFloat01() * 2.0F * Constants::PI;
|
||||
} else {
|
||||
const Vec2 TO = *target - enemy.getCenter();
|
||||
angle = std::atan2(TO.y, TO.x) + (Constants::PI / 2.0F);
|
||||
}
|
||||
} else {
|
||||
angle = randFloat01() * 2.0F * Constants::PI;
|
||||
}
|
||||
if (action.jitter_rad > 0.0F) {
|
||||
angle += (randFloat01() - 0.5F) * 2.0F * action.jitter_rad;
|
||||
}
|
||||
|
||||
// Localitzem l'índex de l'enemic per construir l'owner_id. Evitem
|
||||
// aritmètica de punters sobre Enemy (tipus polimòrfic — UB si la
|
||||
// jerarquia canvia); cerca lineal a l'array (mida petita, no és hot path).
|
||||
std::size_t enemy_index = 0;
|
||||
for (std::size_t i = 0; i < ctx.enemies.size(); ++i) {
|
||||
if (&ctx.enemies[i] == &enemy) {
|
||||
enemy_index = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
const auto OWNER = static_cast<uint8_t>(Defaults::Entities::ENEMY_OWNER_BASE + enemy_index);
|
||||
slot->fire(enemy.getCenter(), angle, OWNER, action.bullet_speed, cfg);
|
||||
}
|
||||
} // namespace
|
||||
|
||||
void dispatchEvent(Systems::Collision::Context& ctx, Enemy& enemy, EnemyEventType event, uint8_t shooter_id, const Bullet* bullet) {
|
||||
@@ -153,6 +240,9 @@ namespace Systems::EnemyEvents {
|
||||
case EnemyActionType::FLASH:
|
||||
enemy.triggerFlash();
|
||||
break;
|
||||
case EnemyActionType::FIRE_BULLET:
|
||||
doFireBullet(ctx, enemy, action);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -114,8 +114,8 @@ namespace Title {
|
||||
}
|
||||
|
||||
void ShipAnimator::init() {
|
||||
auto shape_p1 = Graphics::ShapeLoader::load("ship.shp");
|
||||
auto shape_p2 = Graphics::ShapeLoader::load("ship2.shp");
|
||||
auto shape_p1 = Graphics::ShapeLoader::load("ship/arrow.shp");
|
||||
auto shape_p2 = Graphics::ShapeLoader::load("ship/wedge.shp");
|
||||
|
||||
ships_[0].player_id = 1;
|
||||
if (shape_p1 && shape_p1->isValid()) {
|
||||
|
||||
@@ -3,8 +3,8 @@
|
||||
//
|
||||
// Manté la mateixa màquina d'estats
|
||||
// (ENTERING → FLOATING → EXITING) però treballa amb posicions Vec3 i emet
|
||||
// wireframes a través d'una `Camera3D`. La geometria s'extrau de `ship.shp`
|
||||
// (P1) i `ship2.shp` (P2) per extrusió en Z.
|
||||
// wireframes a través d'una `Camera3D`. La geometria s'extrau de
|
||||
// `ship/arrow.shp` (P1) i `ship/wedge.shp` (P2) per extrusió en Z.
|
||||
|
||||
#pragma once
|
||||
|
||||
|
||||
Reference in New Issue
Block a user