> 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/inventory.md).

# Inventario

### Descripción general

All Out ofrece una API de inventario de jugador que te permite crear objetos, dárselos a los jugadores y que los jugadores organicen, usen y suelten objetos en tu juego.

Hay tres conceptos básicos:

* **Definiciones de objeto** (`Item_Definition`): qué es un objeto *es* (nombre/icono/apilamiento + tus propios campos)
* **Instancias de objeto** (`Item_Instance`): una pila real en el mundo/en un inventario
* **Inventarios** (`Inventory`): un contenedor de espacios que almacena instancias de objeto

Cada jugador tiene un inventario integrado en `player.default_inventory`.

### Persistencia

Si quieres que los inventarios de los jugadores se guarden automáticamente entre sesiones de juego, activa la casilla **Auto Save Player Inventory** en la **Editar → Configuración del juego** sección del editor.

<figure><img src="https://864968843-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FzA8RGUKJ88fD0oXVALlz%2Fuploads%2Fgit-blob-d2182c0249148fa7a8df5e6629073d431d7dedaf%2Fimage.png?alt=media" alt="Player inventory settings in Game Config"><figcaption></figcaption></figure>

{% hint style="info" %}
Configura ambas opciones por programación en `ao_before_scene_load`, antes de que se creen o restauren los inventarios de los jugadores:

```csl
ao_before_scene_load :: proc() {
    Scene.set_player_inventory_capacity(32);
    Scene.set_auto_save_player_inventory(true);
}
```

La capacidad configurada es el mínimo usado durante una restauración persistente; se conserva una capacidad mayor almacenada en la partida guardada. Cambiar la opción de la escena en `Player.ao_start` es demasiado tarde para el `default_inventory` restaurado de ese jugador y no lo redimensiona. Usa `Items.set_capacity` cuando necesites redimensionar intencionadamente un inventario existente. Reducirlo falla si un espacio que se va a quitar contiene un objeto.
{% endhint %}

### Inventarios de jugador con nombre

Crea inventarios persistentes solo durante el `ao_start` del jugador. Cada clave pertenece a ese jugador:

```csl
bank: Inventory;

ao_start :: method() {
    bank = Items.create_player_inventory(this, "bank", capacity=48);
}
```

La llamada devuelve sincrónicamente un inventario restaurado. Repetirla con el mismo jugador y la misma clave durante `ao_start` devuelve el mismo inventario sin restablecer su contenido ni su capacidad. Las llamadas fuera del `ao_start` ao\_start de ese jugador fallan incluso si el inventario ya existe; guarda el inventario devuelto en un campo para usarlo más adelante. Las claves solo pueden contener letras, números, guiones bajos o guiones y no pueden estar vacías. Mantén la clave estable entre versiones del juego.

Los inventarios con nombre se guardan automáticamente de forma independiente de la opción de guardado automático del inventario predeterminado. El motor los guarda y los destruye cuando el jugador se va; no añadas una llamada correspondiente a `destroy_inventory` . Quitar un registro de tu juego no borra sus datos guardados. Usa con el inventario devuelto las APIs existentes de movimiento de objetos y de dibujo de inventario.

El inventario predeterminado conserva su `$AO.inventory` clave de guardado, formato codificado y ruta de obtención existentes. Los inventarios con nombre usan entradas de guardado separadas `$AO.inventory_<key>` . Ambos pasan por la ruta de inicio del jugador existente; no hace falta migración del inventario predeterminado.

Para un inventario guardado, la capacidad es la mayor entre la capacidad inicial solicitada y la capacidad guardada. Usa `Items.set_capacity` para cambios posteriores. Registra las definiciones de objeto antes de que se unan jugadores.

Después de modificar directamente los `campos @ao_serialize` de un objeto, llama a `Items.mark_item_dirty(item)` para guardar el inventario que lo contiene. Las operaciones normales de inventario ya marcan los cambios como pendientes de guardar. La red CSL replica los campos automáticamente.

### Referencia de la API de inventario

```go
Scene :: struct {
    get_player_inventory_capacity :: proc() -> int;
    set_player_inventory_capacity :: proc(capacity: int);
    get_auto_save_player_inventory :: proc() -> bool;
    set_auto_save_player_inventory :: proc(enabled: bool);
}

Item_Definition_Desc :: struct {
    id:         string;
    name:       string;
    icon:       Texture_Asset;
    stack_size: s64;    // 1 = no apilable, -1 = infinito
    tier:       Item_Tier;
}

Items :: struct {
    // Inventarios
    create_inventory  :: proc(unique_id: string, capacity: s64) -> Inventory;
    create_player_inventory :: proc(player: Player, key: string, capacity: s64) -> Inventory;
    destroy_inventory :: proc(inventory: Inventory) -> bool;
    mark_item_dirty :: proc(item: Item_Instance);
    set_capacity      :: proc(inventory: Inventory, capacity: s64);

    // Definiciones de objeto + instancias
    register_item_definition :: proc(desc: Item_Definition_Desc, $Definition_Type: typeid = Item_Definition, instance_type: typeid = Item_Instance) -> Definition_Type;
    create_item_instance     :: proc(definition: Item_Definition, count: s64 = 1) -> Item_Instance;
    create_item_instance     :: proc(definition: Item_Definition, $T: typeid, count: s64 = 1) -> T;
    destroy_item_instance    :: proc(instance: Item_Instance, count: s64 = -1);

    // Mover objetos
    can_move_item_to_inventory                  :: proc(instance: Item_Instance, inventory: Inventory, will_destroy_item: ref bool) -> bool;
    move_item_to_inventory                      :: proc(instance: Item_Instance, inventory: Inventory);
    move_as_many_items_as_possible_to_inventory :: proc(instance: Item_Instance, inventory: Inventory, destroyed_item: ref bool) -> s64;
    remove_item_from_inventory                  :: proc(instance: Item_Instance, inventory: Inventory);
    can_move_all_items                          :: proc(entries: []Item_Transaction_Entry) -> bool;
    move_all_items                              :: proc(entries: []Item_Transaction_Entry);
    can_swap_items                              :: proc(inventory_a: Inventory, inventory_b: Inventory, slot_a: s64, slot_b: s64) -> bool;
    swap_items                                  :: proc(inventory_a: Inventory, inventory_b: Inventory, slot_a: s64, slot_b: s64);

    // Consultas
    calculate_room_in_inventory_for_item :: proc(definition: Item_Definition, inventory: Inventory) -> s64;
    destroy_all_items :: proc(inventory: Inventory);

    // Interfaz de usuario (opcional)
    draw_inventory :: proc(rect: Rect, inventory: Inventory, options: Inventory_Draw_Options) -> bool;
    draw_hotbar    :: proc(player: Player, inventory: Inventory, options: Inventory_Draw_Options) -> Draw_Hotbar_Result;
}

Inventory :: class {
    slots :: method() -> Inventory_Slot_Iterator;
    get_item :: proc(inventory: Inventory, index: s64) -> Item_Instance;
    has_item_id :: proc(inventory: Inventory, item_id: string) -> bool;
    capacity: s64 #read_only;
}

Item_Definition :: class {
    get_icon :: method() -> Texture_Asset;
    id: string #read_only;
    name: string #read_only;
    type: typeid #read_only;
    instance_type: typeid #read_only;
    tier: Item_Tier #read_only;
}

Item_Instance :: class {
    quantity:   s64       #read_only;  // recuento de la pila
    slot_index: s64       #read_only;  // espacio en el inventario padre
    inventory:  Inventory #read_only;  // inventario padre (null si no está en uno)
    get_definition :: method() -> Item_Definition;
}

Item_Transaction_Entry :: struct {
    instance: Item_Instance;
    inventory: Inventory;
}

Inventory_Draw_Options :: struct {
    title: string;
    show_exit_button: bool;
    show_scroll_bar: bool;
    show_background: bool;
    allow_drag_drop: bool;
    drag_drop_color_multiplier: v4;
    hotbar_item_count: int;
    columns: int;
    rows: int;
    force_select_hotbar_index: int;
    hide_bag_button: bool;
    enable_selection: bool;
    scroll_item_selection: bool;
    keyboard_item_selection: bool;
    enable_use_from_hotbar: bool;
    on_before_draw: (proc(item: Item_Instance, rect: Rect));
    on_after_draw: (proc(item: Item_Instance, rect: Rect));

    inventory_default :: proc() -> Inventory_Draw_Options; // hotbar_item_count = 0
    hotbar_default :: proc() -> Inventory_Draw_Options;    // hotbar_item_count = 6
}

Draw_Hotbar_Result :: struct {
    selected_item: Item_Instance;
    selected_item_index: s64;
    dropped_item: Item_Instance;
    entire_rect: Rect;
    inventory_open: bool;
    inventory_open_t: float;
}
```

### Inicio rápido (registrar + dar un objeto)

Registra las definiciones de objeto una vez en `ao_before_scene_load`, luego crea instancias y muévelas al inventario de un jugador.

```go
// Opcional: tipos de objeto personalizados
Weapon_Definition :: class : Item_Definition {
    damage: s64;
}

Weapon_Item :: class : Item_Instance {
    @ao_serialize durability: s64;
}

sword_defn: Weapon_Definition;

ao_before_scene_load :: proc() {
    sword_defn = Items.register_item_definition(
        {id="sword", name="Iron Sword", icon=get_asset(Texture_Asset, "icons/sword.png"), stack_size=1, tier=.COMMON},
        Weapon_Definition,
        Weapon_Item
    );
    sword_defn.damage = 10;
}

give_sword :: proc(player: Player) {
    item := Items.create_item_instance(sword_defn, Weapon_Item);

    will_destroy: bool;
    if Items.can_move_item_to_inventory(item, player.default_inventory, ref will_destroy) {
        item.durability = 100;
        Items.move_item_to_inventory(item, player.default_inventory);
        // Si will_destroy es true, el objeto fue absorbido en una pila existente
        // y no deberías usar esta referencia al objeto después del movimiento.
    }
    else {
        // Inventario lleno → destruir la instancia que creamos
        Items.destroy_item_instance(item);
    }
}
```

Cada `Item_Instance` campo personalizado que deba sobrevivir al guardado y la restauración del inventario necesita `campos @ao_serialize`@ao\_serialize. Un campo sin él vuelve a su valor predeterminado normal después de la restauración. En clases personalizadas anidadas, anota tanto el campo contenedor como cada campo anidado que deba persistir.

Persiste una selección como un arma equipada usando `Save.save_item_reference(player, key, item)` y `Save.get_item_reference(player, key)`. El objeto puede pertenecer al `default_inventory` inventario con guardado automático del jugador o a cualquier inventario de jugador con nombre. Las referencias sobreviven al reordenamiento de espacios dentro de ese inventario y al volver a conectarse. Quitar, soltar, destruir o transferir un objeto a cualquier otro inventario invalida sus referencias anteriores, incluso para el mismo jugador o aunque el objeto luego regrese. Una pila consumida por una fusión también invalida sus referencias. Crea los inventarios con nombre antes de resolver selecciones guardadas. El inventario sigue siendo la autoridad para la propiedad y los campos del objeto, así que no copies esos valores a Save.

`item.entry_id_in_inventory` expone el número de entrada del inventario actual para diagnóstico. Es positivo mientras el objeto está poseído, permanece sin cambios cuando se reordenan los espacios, cambia en las transferencias y se vuelve `0` cuando se separa. Las referencias guardadas incluyen la clave del inventario junto con este número. Usa `Save.save_item_reference` en lugar de persistir el número directamente.

### Mostrar la barra rápida

Para mostrar en pantalla la interfaz estándar de la barra rápida del inventario, llama a `Items.draw_hotbar` dentro del `método ao_late_update` de tu Player. Sin ello, el sistema de inventario funciona internamente, ¡pero el jugador no lo verá!

```go
Player :: class : Player_Base {
    ao_late_update :: method(dt: float) {
        if this.is_local_or_server() {
            draw_player_hotbar(this);
        }
    }
}

draw_player_hotbar :: proc(player: Player) {
    options := Inventory_Draw_Options.hotbar_default();
    options.hide_bag_button = false;

    result := Items.draw_hotbar(player, player.default_inventory, options);

    // result.selected_item es el objeto actualmente resaltado (o null)
    // result.inventory_open te dice si se está mostrando la interfaz completa de la mochila
}
```

{% hint style="info" %}
`Items.draw_hotbar` gestiona por ti toda la interfaz de la barra rápida + el alternador de mochila. Devuelve un `Draw_Hotbar_Result` con los campos `selected_item`, `selected_item_index`, `inventory_open` y  `dropped_item` .
{% endhint %}

{% hint style="warning" %}
`hide_bag_button = true` impide que el jugador abra la mochila. Si la mochila ya está abierta, se cerrará, así que úsalo solo cuando los jugadores todavía tengan otra forma de acceder a objetos importantes.
{% endhint %}

### Comprobar si un jugador ya tiene un objeto

Un patrón común es dar un objeto solo si el jugador no tiene ya uno:

Usa el ID registrado de la definición:

```go
give_sword_if_needed :: proc(player: Player) {
    if player.default_inventory.has_item_id("sword") return;
    give_sword(player);
}
```

También puedes comprobar cuánto espacio queda para un tipo específico de objeto antes de crearlo:

```go
room := Items.calculate_room_in_inventory_for_item(sword_defn, player.default_inventory);
if room > 0 {
    give_sword(player);
}
```

### Inventarios personalizados

Puedes crear inventarios que no estén vinculados a un jugador, para cosas como:

* Cofres / contenedores de almacenamiento
* Equipos de peces/mobs

```go
chest_inventory := Items.create_inventory("chest_01", 12);
```

Para acceder a los objetos:

```go
for item, slot: chest_inventory.slots() if item != null {
    defn := item.get_definition();
    log_info("Slot %: %", {slot, defn.name});
}
```

`slots()` visita cada espacio en orden. `item` es `null` para un espacio vacío, y la segunda variable opcional del bucle es el índice real del espacio. Usa `get_item(index)` cuando necesites acceso aleatorio a un espacio.

### Movimientos atómicos de objetos

Usa una transacción cuando varios movimientos de objetos deban tener todos éxito o todos fallar:

```go
moves: [..]Item_Transaction_Entry;
moves.append({instance = sword, inventory = player.default_inventory});
moves.append({instance = shield, inventory = player.default_inventory});

if Items.can_move_all_items(moves) {
    Items.move_all_items(moves);
}
```

Cada instancia de objeto puede aparecer solo una vez en la transacción. Llama siempre primero a `can_move_all_items` ; `move_all_items` lanza una aserción si la transacción completa ya no cabe. Un movimiento puede fusionar una pila y destruir la instancia movida, así que no sigas usando referencias al objeto después de la transacción.

### Objetos soltados

Si quieres que los jugadores suelten objetos en el mundo (y que otros jugadores los recojan), usa el sistema de objetos soltados:

```go
import "core:dropped_items"
```

#### Generar un objeto soltado

```go
item := Items.create_item_instance(sword_defn, Weapon_Item);
dropped := Dropped_Item.spawn(player.entity.world_position, item);
```

#### Animación de soltar + ajuste a la navmesh

```go
gameplay_rng: u64;

dropped.do_spawn_animation(ref gameplay_rng, navmesh); // la navmesh es opcional
```

#### Gestionar arrastrar y soltar desde la interfaz de la barra rápida

Si usas `Items.draw_hotbar`, los jugadores pueden arrastrar un objeto fuera de la interfaz para soltarlo. `Dropped_Item.handle_dropped_item` lo elimina del inventario y genera una entidad de objeto soltado.

```go
result := Items.draw_hotbar(player, player.default_inventory, Inventory_Draw_Options.hotbar_default());

dropped: Dropped_Item;
if Dropped_Item.handle_dropped_item(result, player.entity.world_position, ref dropped) {
    dropped.do_spawn_animation(ref gameplay_rng, navmesh);
}
```

{% hint style="info" %}
Los objetos soltados desaparecen automáticamente con el tiempo (con una barra de advertencia). Mantener pulsado el botón de interactuar sobre un objeto reinicia su temporizador de desaparición.
{% endhint %}

{% hint style="info" %}
Puedes hacer que un objeto soltado sea exclusivo (solo visible/recogible por un jugador) usando `dropped.set_exclusive(player)`.
{% endhint %}
