tune(enemy): big_pentagon orb circular, firework petit per hit, sense wounded chain
This commit is contained in:
@@ -1,15 +1,17 @@
|
|||||||
name: big_pentagon
|
name: big_pentagon
|
||||||
ai_type: big_pentagon # Validat contra el directori; mapeja a EnemyType::BIG_PENTAGON.
|
ai_type: big_pentagon # Validat contra el directori; mapeja a EnemyType::BIG_PENTAGON.
|
||||||
|
|
||||||
# Reusa la shape de pentagon però a escala 2.0 — primer enemic gegant del joc.
|
# 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:
|
shape:
|
||||||
path: enemy_pentagon.shp
|
path: enemy_big_orb.shp
|
||||||
scale: 2.0
|
scale: 1.5
|
||||||
collision_factor: 1.0
|
collision_factor: 1.0
|
||||||
|
|
||||||
physics:
|
physics:
|
||||||
mass: 20.0 # Massa gran: la bala el frena poc, els trossos volen amb la força de la bala.
|
mass: 50.0 # Molt pesat: una bala el frena un poc però no el "envia a passejar".
|
||||||
speed: 30.0 # Avanç lent, com a "tanc carregant".
|
speed: 50.0 # Avança decidit cap al ship (no és lent passiu, és amenaça constant).
|
||||||
rotation_delta_min: 0.3
|
rotation_delta_min: 0.3
|
||||||
rotation_delta_max: 1.5
|
rotation_delta_max: 1.5
|
||||||
restitution: 1.0
|
restitution: 1.0
|
||||||
@@ -17,11 +19,12 @@ physics:
|
|||||||
angular_damping: 0.0
|
angular_damping: 0.0
|
||||||
|
|
||||||
ai:
|
ai:
|
||||||
# Persecució lenta del ship més proper: amb chase_strength baix la inèrcia
|
# Persecució contínua del ship més proper. chase_strength alt (1.0 = ~1s
|
||||||
# és molt notable, fa el comportament del "boss lent" característic.
|
# per realinear-se) perquè, encara que una bala l'empentja lateralment,
|
||||||
|
# ràpidament torna a posar la seua proa cap al jugador.
|
||||||
movement:
|
movement:
|
||||||
type: chase
|
type: chase
|
||||||
chase_strength: 0.3
|
chase_strength: 1.0
|
||||||
|
|
||||||
animation:
|
animation:
|
||||||
pulse:
|
pulse:
|
||||||
@@ -49,31 +52,31 @@ spawn:
|
|||||||
invulnerability_brightness_end: 0.7
|
invulnerability_brightness_end: 0.7
|
||||||
invulnerability_scale_start: 0.0
|
invulnerability_scale_start: 0.0
|
||||||
invulnerability_scale_end: 1.0
|
invulnerability_scale_end: 1.0
|
||||||
safety_distance: 72.0 # Doble del normal — és el doble de gran.
|
safety_distance: 54.0 # 1.5× del normal (alineat amb scale 1.5).
|
||||||
|
|
||||||
colors:
|
colors:
|
||||||
normal: [0, 180, 255] # Blau elèctric per distingir-lo del pentagon cyan.
|
normal: [66, 195, 208] # #42C3D0 — turquesa-cyan distintiu per al boss.
|
||||||
wounded: [255, 220, 60]
|
wounded: [255, 220, 60]
|
||||||
|
|
||||||
score: 500 # 5x un enemic normal: aguanta 10x més.
|
score: 500 # 5x un enemic normal: aguanta 10x més.
|
||||||
|
|
||||||
# Estrenant el sistema HP: 10 unitats. Cada bala fa decrease_health + flash
|
# Estrenant el sistema HP: 10 unitats. Cada bala fa decrease_health + flash
|
||||||
# + create_debris_partial (xip a 0.3x). En el 10è hit, on_no_health
|
# + create_debris_partial (xip a 0.3x) + create_fireworks_small (espurna).
|
||||||
# encadena set_hurt (entra wounded). Wound expira → destroy → on_destroy
|
# Al 10è hit (HP=0), on_no_health encadena destroy directe — sense passar
|
||||||
# fa l'explosió completa.
|
# per wounded (com Star). 10 HP ja és prou dificultat sense afegir un hit
|
||||||
|
# extra.
|
||||||
health: 10
|
health: 10
|
||||||
|
|
||||||
events:
|
events:
|
||||||
on_hit:
|
on_hit:
|
||||||
- action: decrease_health # primer: si arriba a 0 dispara on_no_health
|
- action: decrease_health # primer: si arriba a 0 dispara on_no_health
|
||||||
- action: flash # feedback visual de damage parcial
|
- action: flash # feedback visual de damage parcial
|
||||||
- action: create_debris_partial # xip a 0.3x mida (sense ser letal)
|
- action: create_debris_partial # xip a 0.3x mida (sense ser letal)
|
||||||
- action: apply_impulse # empenta el cos (sense will_die és segur en hit no-letal)
|
- action: create_fireworks_small # espurna a cada hit (12 punts, lent)
|
||||||
|
- action: apply_impulse # empenta el cos (skip si will_die)
|
||||||
on_no_health:
|
on_no_health:
|
||||||
- action: set_hurt # entra wounded; segon hit durant wounded matarà
|
- action: destroy # mort directa, sense wounded
|
||||||
on_hurt_end:
|
|
||||||
- action: destroy
|
|
||||||
on_destroy:
|
on_destroy:
|
||||||
- action: add_score
|
- action: add_score
|
||||||
- action: create_debris # explosió completa
|
- action: create_debris # explosió completa
|
||||||
- action: create_fireworks
|
- action: create_fireworks
|
||||||
|
|||||||
@@ -0,0 +1,32 @@
|
|||||||
|
# enemy_big_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è
|
||||||
|
# es renderitza a escala 1.5x i ha de llegir-se com a amenaça gran.
|
||||||
|
# - Anell exterior: dodecàgon (12 vèrtexs) — aparença circular suau, radi 20.
|
||||||
|
# - Anell interior: hexàgon (6 vèrtexs, rotat 30°) — radi 10.
|
||||||
|
# - 6 radis curts que connecten l'anell interior amb l'exterior.
|
||||||
|
# - Petit "+" central com a nucli.
|
||||||
|
# Bounding radius natiu = 20 (alineat amb la resta d'enemics).
|
||||||
|
|
||||||
|
name: enemy_big_orb
|
||||||
|
scale: 1.0
|
||||||
|
center: 0, 0
|
||||||
|
|
||||||
|
# Anell exterior (dodecàgon, vèrtex apuntant amunt)
|
||||||
|
polyline: 0,-20 10,-17.32 17.32,-10 20,0 17.32,10 10,17.32 0,20 -10,17.32 -17.32,10 -20,0 -17.32,-10 -10,-17.32 0,-20
|
||||||
|
|
||||||
|
# Anell interior (hexàgon, vèrtex apuntant a la dreta — rotat 30° respecte l'exterior)
|
||||||
|
polyline: 5,-8.66 10,0 5,8.66 -5,8.66 -10,0 -5,-8.66 5,-8.66
|
||||||
|
|
||||||
|
# 6 radis: del vèrtex de l'hexàgon interior al vèrtex corresponent del dodecàgon exterior
|
||||||
|
line: 5,-8.66 10,-17.32
|
||||||
|
line: 10,0 20,0
|
||||||
|
line: 5,8.66 10,17.32
|
||||||
|
line: -5,8.66 -10,17.32
|
||||||
|
line: -10,0 -20,0
|
||||||
|
line: -5,-8.66 -10,-17.32
|
||||||
|
|
||||||
|
# Nucli central: petit "+" (2 segments creuats, radi 3)
|
||||||
|
line: -3,0 3,0
|
||||||
|
line: 0,-3 0,3
|
||||||
@@ -7,7 +7,7 @@ metadata:
|
|||||||
description: "Progressive difficulty curve from novice to expert"
|
description: "Progressive difficulty curve from novice to expert"
|
||||||
|
|
||||||
stages:
|
stages:
|
||||||
# STAGE 1: Tutorial - Mix de tots 5 tipus per mostrar-los junts (inclou big_pentagon)
|
# STAGE 1: Tutorial - 4 tipus (sense star: les bales fan injugable el test).
|
||||||
- stage_id: 1
|
- stage_id: 1
|
||||||
total_enemies: 50
|
total_enemies: 50
|
||||||
spawn_config:
|
spawn_config:
|
||||||
@@ -15,10 +15,9 @@ stages:
|
|||||||
initial_delay: 0.3
|
initial_delay: 0.3
|
||||||
spawn_interval: 0.4
|
spawn_interval: 0.4
|
||||||
enemy_distribution:
|
enemy_distribution:
|
||||||
pentagon: 20
|
pentagon: 30
|
||||||
cuadrado: 20
|
cuadrado: 25
|
||||||
molinillo: 20
|
molinillo: 25
|
||||||
star: 20
|
|
||||||
big_pentagon: 20
|
big_pentagon: 20
|
||||||
difficulty_multipliers:
|
difficulty_multipliers:
|
||||||
speed_multiplier: 0.7
|
speed_multiplier: 0.7
|
||||||
|
|||||||
@@ -33,3 +33,13 @@ namespace Defaults::Enemies::Debris {
|
|||||||
constexpr float PARTIAL_PIECE_SCALE = 0.3F;
|
constexpr float PARTIAL_PIECE_SCALE = 0.3F;
|
||||||
|
|
||||||
} // namespace Defaults::Enemies::Debris
|
} // namespace Defaults::Enemies::Debris
|
||||||
|
|
||||||
|
namespace Defaults::Enemies::Fireworks {
|
||||||
|
|
||||||
|
// Paràmetres del firework "petit" per a l'acció CREATE_FIREWORKS_SMALL
|
||||||
|
// (feedback per impacte parcial en enemics HP>1). Pocs punts i baixa
|
||||||
|
// velocitat: una espurna breu, no una explosió.
|
||||||
|
constexpr int SMALL_N_POINTS = 20;
|
||||||
|
constexpr float SMALL_SPEED = 250.0F;
|
||||||
|
|
||||||
|
} // namespace Defaults::Enemies::Fireworks
|
||||||
|
|||||||
@@ -193,6 +193,7 @@ namespace {
|
|||||||
if (s == "create_debris") { return EnemyActionType::CREATE_DEBRIS; }
|
if (s == "create_debris") { return EnemyActionType::CREATE_DEBRIS; }
|
||||||
if (s == "create_debris_partial") { return EnemyActionType::CREATE_DEBRIS_PARTIAL; }
|
if (s == "create_debris_partial") { return EnemyActionType::CREATE_DEBRIS_PARTIAL; }
|
||||||
if (s == "create_fireworks") { return EnemyActionType::CREATE_FIREWORKS; }
|
if (s == "create_fireworks") { return EnemyActionType::CREATE_FIREWORKS; }
|
||||||
|
if (s == "create_fireworks_small") { return EnemyActionType::CREATE_FIREWORKS_SMALL; }
|
||||||
if (s == "apply_impulse") { return EnemyActionType::APPLY_IMPULSE; }
|
if (s == "apply_impulse") { return EnemyActionType::APPLY_IMPULSE; }
|
||||||
if (s == "decrease_health") { return EnemyActionType::DECREASE_HEALTH; }
|
if (s == "decrease_health") { return EnemyActionType::DECREASE_HEALTH; }
|
||||||
if (s == "flash") { return EnemyActionType::FLASH; }
|
if (s == "flash") { return EnemyActionType::FLASH; }
|
||||||
|
|||||||
@@ -18,15 +18,16 @@ enum class EnemyEventType : uint8_t {
|
|||||||
};
|
};
|
||||||
|
|
||||||
enum class EnemyActionType : uint8_t {
|
enum class EnemyActionType : uint8_t {
|
||||||
SET_HURT, // Entra estat wounded (o destrueix si ja era wounded)
|
SET_HURT, // Entra estat wounded (o destrueix si ja era wounded)
|
||||||
DESTROY, // Dispara on_destroy + desactiva físicament
|
DESTROY, // Dispara on_destroy + desactiva físicament
|
||||||
ADD_SCORE, // Suma config.score al shooter + floating score
|
ADD_SCORE, // Suma config.score al shooter + floating score
|
||||||
CREATE_DEBRIS, // Explosió de debris amb herència de velocitat
|
CREATE_DEBRIS, // Explosió de debris amb herència de velocitat
|
||||||
CREATE_DEBRIS_PARTIAL, // Debris de xip parcial (trossos a escala 0.3, per hits HP>1)
|
CREATE_DEBRIS_PARTIAL, // Debris de xip parcial (trossos a escala 0.3, per hits HP>1)
|
||||||
CREATE_FIREWORKS, // Burst radial de firework
|
CREATE_FIREWORKS, // Burst radial de firework
|
||||||
APPLY_IMPULSE, // Aplica l'impuls de la bala impactant
|
CREATE_FIREWORKS_SMALL, // Burst petit (pocs punts, poca velocitat) — feedback per hit
|
||||||
DECREASE_HEALTH, // Decrementa health_; si <=0, dispatcha ON_NO_HEALTH
|
APPLY_IMPULSE, // Aplica l'impuls de la bala impactant
|
||||||
FLASH, // Flash visual breu (feedback per impacte parcial)
|
DECREASE_HEALTH, // Decrementa health_; si <=0, dispatcha ON_NO_HEALTH
|
||||||
|
FLASH, // Flash visual breu (feedback per impacte parcial)
|
||||||
};
|
};
|
||||||
|
|
||||||
struct EnemyAction {
|
struct EnemyAction {
|
||||||
|
|||||||
@@ -83,36 +83,6 @@ namespace Systems::Collision {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void detectWoundedChain(Context& ctx) {
|
|
||||||
const std::size_t N = ctx.enemies.size();
|
|
||||||
for (std::size_t i = 0; i < N; i++) {
|
|
||||||
Enemy& a = ctx.enemies[i];
|
|
||||||
if (!a.isCollidable()) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
for (std::size_t j = i + 1; j < N; j++) {
|
|
||||||
Enemy& b = ctx.enemies[j];
|
|
||||||
if (!b.isCollidable()) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
const bool A_WOUNDED = a.isWounded();
|
|
||||||
const bool B_WOUNDED = b.isWounded();
|
|
||||||
if (A_WOUNDED == B_WOUNDED) {
|
|
||||||
continue; // ambos sanos o ambos heridos: nada que propagar
|
|
||||||
}
|
|
||||||
if (!Physics::checkCollision(a, b, Defaults::Game::COLLISION_WOUNDED_CHAIN_AMPLIFIER)) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
// El sano queda herido, propagando el shooter original.
|
|
||||||
if (A_WOUNDED) {
|
|
||||||
b.hurt(a.getLastHitBy());
|
|
||||||
} else {
|
|
||||||
a.hurt(b.getLastHitBy());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void detectShipEnemy(Context& ctx) {
|
void detectShipEnemy(Context& ctx) {
|
||||||
constexpr float AMPLIFIER = Defaults::Game::COLLISION_SHIP_ENEMY_AMPLIFIER;
|
constexpr float AMPLIFIER = Defaults::Game::COLLISION_SHIP_ENEMY_AMPLIFIER;
|
||||||
|
|
||||||
@@ -263,7 +233,9 @@ namespace Systems::Collision {
|
|||||||
void detectAll(Context& ctx) {
|
void detectAll(Context& ctx) {
|
||||||
processWoundedDeaths(ctx); // expiran ANTES de ser tocadas por bala este frame
|
processWoundedDeaths(ctx); // expiran ANTES de ser tocadas por bala este frame
|
||||||
detectBulletEnemy(ctx);
|
detectBulletEnemy(ctx);
|
||||||
detectWoundedChain(ctx); // un herit pot ferir a un sa al fregar-lo
|
// 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
|
||||||
|
// "ferit-toca-sa → ferit" queda permanentment fora.
|
||||||
detectShipEnemy(ctx);
|
detectShipEnemy(ctx);
|
||||||
detectBulletPlayer(ctx);
|
detectBulletPlayer(ctx);
|
||||||
detectEnemyBulletShip(ctx);
|
detectEnemyBulletShip(ctx);
|
||||||
|
|||||||
@@ -56,12 +56,6 @@ namespace Systems::Collision {
|
|||||||
// al `last_hit_by_` del enemy (si está set).
|
// al `last_hit_by_` del enemy (si está set).
|
||||||
void processWoundedDeaths(Context& ctx);
|
void processWoundedDeaths(Context& ctx);
|
||||||
|
|
||||||
// Si un enemy herido colisiona con uno sano (ni herido ni invulnerable),
|
|
||||||
// el sano también queda herido (efecto cadena). Propaga `last_hit_by_` para
|
|
||||||
// que el shooter original siga acreditándose la muerte en cascada. El rebote
|
|
||||||
// físico ya lo resuelve PhysicsWorld; aquí solo propagamos el estado.
|
|
||||||
void detectWoundedChain(Context& ctx);
|
|
||||||
|
|
||||||
// Detecta colisiones ship → enemy. Si hit, llama on_player_hit(player_id).
|
// Detecta colisiones ship → enemy. Si hit, llama on_player_hit(player_id).
|
||||||
void detectShipEnemy(Context& ctx);
|
void detectShipEnemy(Context& ctx);
|
||||||
|
|
||||||
|
|||||||
@@ -51,14 +51,17 @@ namespace Systems::EnemyEvents {
|
|||||||
piece_scale);
|
piece_scale);
|
||||||
}
|
}
|
||||||
|
|
||||||
void doCreateFireworks(Systems::Collision::Context& ctx, const Enemy& enemy) {
|
// Helper compartit per CREATE_FIREWORKS i CREATE_FIREWORKS_SMALL:
|
||||||
|
// mateixa crida a spawn(), només canvien n_points, initial_speed i
|
||||||
|
// glow_color segons el "tamany" del burst (mort vs feedback per hit).
|
||||||
|
void spawnFireworksForEnemy(Systems::Collision::Context& ctx, const Enemy& enemy, int n_points, float initial_speed, SDL_Color glow_color) {
|
||||||
ctx.firework_manager.spawn(enemy.getCenter(),
|
ctx.firework_manager.spawn(enemy.getCenter(),
|
||||||
Defaults::FX::Firework::DEFAULT_COLOR,
|
Defaults::FX::Firework::DEFAULT_COLOR,
|
||||||
Defaults::FX::Firework::SPEED,
|
initial_speed,
|
||||||
Defaults::FX::Firework::N_POINTS,
|
n_points,
|
||||||
Defaults::FX::Firework::INITIAL_BRIGHTNESS,
|
Defaults::FX::Firework::INITIAL_BRIGHTNESS,
|
||||||
/*glow=*/true,
|
/*glow=*/true,
|
||||||
enemy.getConfig().colors.wounded);
|
glow_color);
|
||||||
}
|
}
|
||||||
|
|
||||||
void doApplyImpulse(Enemy& enemy, const Bullet* bullet) {
|
void doApplyImpulse(Enemy& enemy, const Bullet* bullet) {
|
||||||
@@ -123,7 +126,14 @@ namespace Systems::EnemyEvents {
|
|||||||
spawnDebrisForEnemy(ctx, enemy, bullet, Defaults::Enemies::Debris::PARTIAL_PIECE_SCALE);
|
spawnDebrisForEnemy(ctx, enemy, bullet, Defaults::Enemies::Debris::PARTIAL_PIECE_SCALE);
|
||||||
break;
|
break;
|
||||||
case EnemyActionType::CREATE_FIREWORKS:
|
case EnemyActionType::CREATE_FIREWORKS:
|
||||||
doCreateFireworks(ctx, enemy);
|
// Burst de mort: glow amb el color wounded (daurat) per
|
||||||
|
// marcar la mort com a esdeveniment "calent".
|
||||||
|
spawnFireworksForEnemy(ctx, enemy, Defaults::FX::Firework::N_POINTS, Defaults::FX::Firework::SPEED, enemy.getConfig().colors.wounded);
|
||||||
|
break;
|
||||||
|
case EnemyActionType::CREATE_FIREWORKS_SMALL:
|
||||||
|
// Burst d'impacte: glow amb el color de l'enemic, perquè
|
||||||
|
// l'espurna llegisca com a "tros del propi cos saltant".
|
||||||
|
spawnFireworksForEnemy(ctx, enemy, Defaults::Enemies::Fireworks::SMALL_N_POINTS, Defaults::Enemies::Fireworks::SMALL_SPEED, enemy.getConfig().colors.normal);
|
||||||
break;
|
break;
|
||||||
case EnemyActionType::APPLY_IMPULSE:
|
case EnemyActionType::APPLY_IMPULSE:
|
||||||
if (!will_die) {
|
if (!will_die) {
|
||||||
|
|||||||
Reference in New Issue
Block a user