> For the complete documentation index, see [llms.txt](https://docs.allout.game/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://docs.allout.game/all-out-docs/docs-es/conceptos-centrales-del-motor/effects.md).

# Efectos

Los efectos toman temporalmente el control de una entidad para comportamientos complejos como dashes, ataques, animaciones de comer, cinemáticas o secuencias de muerte/reaparición.

Los efectos pueden usarse en jugadores, NPCs o cualquier entidad. Son especialmente útiles cuando quieres:

* Movimiento que controlas en código (dash/retroceso/rodar)
* "Bloquear" la entrada del jugador para una secuencia corta (comer, revivir, cinemática)
* Una ventana temporizada de estado (invencibilidad, lentitud, aturdimiento)
* UI vinculada a un estado temporal (cuenta atrás de reaparecer)

## Activos vs. pasivos

All Out admite dos tipos de efectos:

* **Efectos activos**: uno a la vez por entidad. Establecer un nuevo efecto activo interrumpe el efecto activo actual.
* **Efectos pasivos**: varios pueden adjuntarse a la vez (buffs/debuffs/efectos de estado apilables).

{% hint style="info" %}
Un efecto activo también se inserta en la lista de efectos de la entidad, así que aparece en `effect_iterator` igual que los efectos pasivos.
{% endhint %}

## Inicio rápido (un efecto temporizado simple)

Crea una clase que herede de `Effect_Base`, y luego adjúntala a una entidad.

```go
Apple_Pop_Effect :: class : Effect_Base {
    sprite: Sprite_Renderer;
    start_scale: v2;
    start_pos: v2;

    effect_start :: method() {
        sprite = entity.get_component(Sprite_Renderer);
        start_scale = entity.local_scale;
        start_pos = entity.local_position;
        set_duration(0.4); // se elimina automáticamente después de 0,4 s
    }

    effect_update :: method(dt: float) {
        t := get_elapsed_time() / 0.4;

        // Aumentar la escala y luego encoger hasta desaparecer
        scale_curve: float;
        if t < 0.3 {
            scale_curve = lerp(1.0, 1.3, Ease.out_back(t / 0.3));
        } else {
            scale_curve = lerp(1.3, 0.0, Ease.in_back((t - 0.3) / 0.7));
        }

        entity.set_local_scale(start_scale * scale_curve);

        // Flotar ligeramente hacia arriba
        rise := Ease.out_quad(t) * 0.5;
        entity.set_local_position({start_pos.x, start_pos.y + rise});

        // Desvanecer cerca del final
        if sprite != null {
            alpha := 1.0 - Ease.in_quad(max(0.0, (t - 0.5) / 0.5));
            sprite.color.w = alpha;
        }
    }

    effect_end :: method(interrupt: bool) {
        // Limpiar al terminar (o al interrumpirse)
        entity.destroy();
    }
}
```

Adjuntarlo como un efecto activo:

```go
effect := new(Apple_Pop_Effect);
entity.set_active_effect(effect);
```

## Ciclo de vida del efecto

Los efectos se basan en callbacks. Implementa solo lo que necesites:

* `effect_start()`: se llama una vez cuando se adjunta el efecto
* `effect_update(dt)`: se llama en cada fotograma
* `effect_late_update(dt)`: se llama en cada fotograma después de `effect_update`
* `effect_draw(dt)`: dibujo solo cosmético en fotogramas renderizables
* `effect_end(interrupt)`: se llama cuando se elimina el efecto

`effect_draw` incluye a los jugadores y omite la resimulación. La UI interactiva debe usar `effect_update`/`effect_late_update`, nunca `effect_draw`.

## Efectos activos (control exclusivo)

Usa `set_active_effect` para efectos que deben ser mutuamente excluyentes (dash, preparación de ataque, secuencia de muerte).

```go
dash := new(Dash_Effect);
dash.direction = {1, 0};
player.entity.set_active_effect(dash);
```

Si la entidad ya tiene un efecto activo, este termina con `interrupt = true`.

## Efectos pasivos (buffs/estado apilables)

Usa `add_passive_effect` para efectos que pueden apilarse (lentitud, veneno, escudo, ventana de invencibilidad).

```go
slow := new(Slow_Effect);
slow.speed_multiplier = 0.5;
slow.set_duration(4.0);
enemy.entity.add_passive_effect(slow);
```

## Referencia de API

### `Effect_Base`

```go
Effect_Base :: class {
    entity: Entity;
    player: Player; // null si este efecto no se añadió a un jugador!

    player_specific: struct {
        freeze_player: bool;
        disable_movement_inputs: bool;
    };

    // Callbacks opcionales
    #interface effect_start       :: proc(c: Effect_Base);
    #interface effect_update      :: proc(c: Effect_Base, dt: float);
    #interface effect_late_update :: proc(c: Effect_Base, dt: float);
    #interface effect_draw        :: proc(c: Effect_Base, dt: float);
    #interface effect_end         :: proc(c: Effect_Base, interrupt: bool);

    // Solo lectura
    start_time: float;
    next_effect: Effect_Base;
    prev_effect: Effect_Base;

    get_elapsed_time :: method() -> float;
    get_duration_remaining :: method() -> float;
    set_duration     :: method(duration: float);
    remove_effect    :: method(interrupt: bool);
}
```

### Aplicar / eliminar efectos

```go
set_active_effect  :: proc(entity: Entity, new_active_effect: $T);
add_passive_effect :: proc(entity: Entity, new_effect: $T);

remove_all_effects :: proc(entity: Entity);
remove_effect      :: proc(entity: Entity, type: typeid, interrupt: bool) -> bool;
```

### Comprobar / iterar efectos

```go
get_effect :: proc(entity: Entity, $T: typeid, mode := Try_Get_Effect_Mode.EXACT_MATCH) -> (T, bool);
has_effect :: proc(entity: Entity, $T: typeid, mode := Try_Get_Effect_Mode.EXACT_MATCH) -> bool;

effect_iterator :: proc(entity: Entity) -> Effect_Iterator;
```

{% hint style="warning" %}
Después de llamar a `remove_effect(...)`, el efecto se desacopla y deja de estar anclado. Su referencia deja de ser válida. **Devuelve de inmediato** y no accedas a `this` de nuevo.
{% endhint %}

## `freeze_player` vs `disable_movement_inputs`

* **`player_specific.freeze_player = true`**: bloquea por completo la posición del jugador.
* **`player_specific.disable_movement_inputs = true`**: ignora la entrada, pero tu efecto aún puede mover al jugador (dash/rodar).

Ambos solo se aplican cuando `player != null` (es decir, el efecto está en una entidad que tiene un `Player` componente).

## Emotes del jugador

Las emotes del jugador usan un efecto activo integrado. Iniciar otra emote interrumpe la emote actual, mientras que cualquier otro efecto activo impide que se inicie una nueva emote. El movimiento interrumpe las emotes y restaura la capa principal de animación, excepto `Emote/T_Pose`, que permite movimiento.

Usa motivos de bloqueo cuando la jugabilidad deba impedir temporalmente que la rueda de emotes inicie una emote:

```go
player.add_emote_block_reason("stunned");

// Más tarde, cuando termine la restricción:
player.remove_emote_block_reason("stunned");
```

Los motivos se cuentan como entradas, así que es importante que las llamadas add/remove coincidan. `remove_emote_block_reason` elimina una entrada coincidente y devuelve si encontró una. Usa `has_emote_block_reason(reason)` para consultar un motivo y `has_any_emote_block_reason()` para consultar si hay alguno activo. También puedes controlar directamente el efecto integrado:

```go
if player.has_any_emote_block_reason() {
    // Al menos un motivo de bloqueo está activo.
}

started := player.try_trigger_emote("Emote/Wave");
cancelled := player.cancel_emote();
```

`try_trigger_emote` devuelve `false` cuando la animación no está equipada, las emotes están bloqueadas o otro tipo de efecto activo controla al jugador. La rueda normal de emotes usa esta misma ruta.

## Ejemplos de efectos

### Dash / rodar (efecto activo)

```go
Roll_Effect :: class : Effect_Base {
    direction: v2;
    original_friction: float;

    effect_start :: method() {
        player_specific.disable_movement_inputs = true;
        original_friction = player.agent.friction;
        player.agent.friction = 0;
        player.animator.state_machine.set_trigger("dodge_roll");
        player.set_facing_right(direction.x > 0);
        set_duration(0.5);
    }

    effect_update :: method(dt: float) {
        player.agent.velocity = direction * 8;
    }

    effect_end :: method(interrupt: bool) {
        player.agent.friction = original_friction;
    }
}
```

### Ventana de invencibilidad (efecto pasivo)

Usa un efecto pasivo para representar el estado invencible, y compruébalo en cualquier lugar donde apliques daño.

```go
Invincible_Effect :: class : Effect_Base {
}

give_invincibility :: proc(player: Player, seconds: float) {
    e := new(Invincible_Effect);
    e.set_duration(seconds);
    player.entity.add_passive_effect(e);
}

// Ejemplo de puerta de daño:
take_damage :: proc(player: Player, amount: s64) {
    if player.entity.has_effect(Invincible_Effect) {
        return;
    }
    // Asume que tu clase Player tiene un campo de salud.
    player.health.take_damage(amount);
}
```

### 2x de tamaño durante unos segundos (efecto pasivo)

```go
Grow_Effect :: class : Effect_Base {
    scale_multiplier: float;
    start_scale: v2;

    effect_start :: method() {
        start_scale = entity.local_scale;
        entity.set_local_scale(start_scale * scale_multiplier);
    }

    effect_end :: method(interrupt: bool) {
        entity.set_local_scale(start_scale);
    }
}

apply_grow :: proc(entity: Entity) {
    if entity.has_effect(Grow_Effect) return;

    e := new(Grow_Effect);
    e.scale_multiplier = 2.0;
    e.set_duration(3.0);
    entity.add_passive_effect(e);
}
```

### Secuencia de muerte / reaparecer (efecto activo con UI local)

```go
Death_Effect :: class : Effect_Base {
    effect_start :: method() {
        player_specific.freeze_player = true;
        player.add_name_invisibility_reason("death");
        player.animator.state_machine.set_trigger("death");
    }

    effect_update :: method(dt: float) {
        time_until_respawn := 5.0 - get_elapsed_time();

        if time_until_respawn <= 0 {
            remove_effect(false);
            return;
        }
    }

    effect_end :: method(interrupt: bool) {
        player.remove_name_invisibility_reason("death");
        respawn_player(player); // definido por el usuario: teletransporta al jugador al punto de aparición
        player.health.reset();
        player.animator.state_machine.set_trigger("RESET");
    }
}

My_Player :: class : Player_Base {
    ao_late_update :: method(dt: float) {
        if !is_local_or_server() return;

        death, found := entity.get_effect(Death_Effect);
        if !found return;

        time_until_respawn := max(0.0, 5.0 - death.get_elapsed_time());
        ts := UI.default_text_settings();
        ts.size = 64;
        rect := UI.get_screen_rect().bottom_center_rect().offset(0, 150);
        UI.text(rect, ts, "Reapareciendo en %", {time_until_respawn.(int) + 1});
    }
}
```

### Bloqueo de cinemática (efecto activo)

```go
Cutscene_Effect :: class : Effect_Base {
    effect_start :: method() {
        player_specific.freeze_player = true;
        set_duration(2.0);
    }
}
```

## Visuales personalizados suaves

La simulación se ejecuta a una tasa fija, mientras que el renderizado puede ir más rápido. Dibujar desde una devolución de llamada de componente se ancla automáticamente a la entidad de ese componente. Para otros casos:

* Envuelve el dibujo en espacio mundial de otra entidad en `UI.begin_world_space_ui(entity)` y `UI.end_world_space_ui()`.
* Para una posición en movimiento que no pertenece a una entidad, mantén un `Position_Interpolation_Helper`. Pasa el desplazamiento de `update(position)` a `UI.push_interpolation_offset`, y luego elimínalo después de dibujar.
* Usa un `Float_Interpolation_Helper` con los campos `persistente para un valor de barra cambiante.` persistente
* Llama a `entity.mark_teleported()` después de un cambio discontinuo de posición.

```go
Smooth_Bar :: class {
    progress: float;
    interpolation: Float_Interpolation_Helper;

    draw :: method(rect: Rect) {
        fill := UI.quad_fill(interpolation.update(progress), .RIGHT);
        UI.quad(rect, core_globals.white_sprite, {0.2, 0.9, 0.3, 1}, params={fill=fill});
    }
}
```

Mantén los ayudantes de interpolación en un componente persistente o en el estado de la UI. Un ayudante creado dentro del método draw no tiene un fotograma anterior desde el que interpolar.

## Buenas prácticas

* Usa `set_active_effect` cuando el efecto “posee” la entidad durante una secuencia corta (dash/ataque/muerte/cinemática).
* Usa `add_passive_effect` para estado apilable (buffs/debuffs/estados).
* Usa `set_duration(...)` para efectos temporizados en lugar de temporizadores manuales.
* Restaura siempre cualquier estado modificado en `effect_end` (fricción, escala, disparadores de animación, etc.).
* Ejecuta los efectos de jugabilidad en la ruta predicha compartida.
* `effect_draw` es solo cosmético. Dibuja la UI interactiva del efecto desde la del jugador `método ao_late_update` en `is_local_or_server()`.
