> 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-basicos-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, NPC o cualquier entidad. Son especialmente útiles cuando quieres:

* Movimiento que controlas en el código (dash/retroceso/rodar)
* “Bloquear” la entrada del jugador durante una secuencia corta (comer, revivir, cinemática)
* Una ventana temporal de estado (invencibilidad, ralentización, aturdimiento)
* UI vinculada a un estado temporal (cuenta atrás de reaparición)

## Activos frente a 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 (bonificaciones/penalizaciones/efectos de estado acumulables).

{% hint style="info" %}
Un efecto activo también se inserta en la lista de efectos de la entidad, por lo 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`, luego adjúntalo 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;

        // Aumenta de tamaño y luego encógelo 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);

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

        // Desvanece al 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) {
        // Limpia al terminar (o si se interrumpe)
        entity.destroy();
    }
}
```

Adjúntalo 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()`: llamada una vez cuando se adjunta el efecto
* `effect_update(dt)`: llamada en cada frame
* `effect_late_update(dt)`: llamada en cada frame después de `effect_update`
* `effect_end(interrupt)`: llamada cuando se elimina el efecto

## Efectos activos (control exclusivo)

Usa `set_active_effect` para efectos que deben ser mutuamente excluyentes (dash, preparación del 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 (bonificaciones / estado acumulables)

Usa `add_passive_effect` para efectos que pueden acumularse (ralentización, 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 la 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_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 inmediatamente** y no accedas a `this` de nuevo.
{% endhint %}

## `freeze_player` vs `disable_movement_inputs`

* **`player_specific.freeze_player = true`**: bloquea completamente la posición del jugador.
* **`player_specific.disable_movement_inputs = true`**: ignora la entrada, pero tu efecto todavía 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

Los emotes del jugador usan un efecto activo integrado. Iniciar otro emote interrumpe el emote actual, mientras que cualquier otro efecto activo impide que se inicie un nuevo emote. El movimiento interrumpe los emotes y restaura la capa principal de animación, excepto para `Emote/T_Pose`, que permite movimiento.

Usa motivos de bloqueo cuando la jugabilidad deba impedir temporalmente que la rueda de emotes inicie un 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 las llamadas coincidentes de añadir/eliminar son importantes. `remove_emote_block_reason` elimina una entrada coincidente y devuelve si encontró una. También puedes consultar o controlar directamente el efecto integrado:

```go
if player.is_emote_blocked() {
    // 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, los emotes están bloqueados o otro tipo de efecto activo controla al jugador. La rueda de emotes normal 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 de invencibilidad y compruébalo dondequiera que 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 filtro 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 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 / reaparición (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 un callback de componente se ancla automáticamente a la entidad de ese componente. Para otros casos:

* Envuelve el dibujo en espacio del mundo para 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 desde `update(position)` a `UI.push_interpolation_offset`y luego extráelo después de dibujar.
* Usa un `Float_Interpolation_Helper` con `UI.quad_fill` para un valor de barra cambiante.
* Llama a `entity.mark_teleported()` después de un cambio de posición discontinuo.

```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 frame anterior del que interpolar.

## Buenas prácticas

* Usa `set_active_effect` cuando el efecto “controla” la entidad durante una secuencia corta (dash/ataque/muerte/cinemática).
* Usa `add_passive_effect` para estado acumulable (buffs/debuffs/estado).
* Usa `set_duration(...)` para efectos temporizados en lugar de temporizadores manuales.
* Restablece siempre cualquier estado modificado en `effect_end` (fricción, escala, disparadores de animación, etc.).
* Ejecuta los efectos de jugabilidad en la ruta predictiva compartida.
* Dibuja la UI relacionada con el efecto desde la del jugador `ao_late_update` y protégela con `is_local_or_server()`.
