port over some more

This commit is contained in:
ra
2026-02-14 23:58:01 -07:00
parent 0222be36c5
commit 61176cd416
107 changed files with 9124 additions and 375 deletions

View File

@@ -167,6 +167,17 @@ defmodule Odinsea.Game.Inventory do
end
end
@doc """
Gets the next available slot number.
Returns nil if the inventory is full (Elixir-style).
"""
def get_next_free_slot(%__MODULE__{} = inv) do
case next_free_slot(inv) do
-1 -> nil
slot -> slot
end
end
defp find_next_slot(items, limit, slot) when slot > limit, do: -1
defp find_next_slot(items, limit, slot) do
@@ -205,6 +216,23 @@ defmodule Odinsea.Game.Inventory do
end
end
@doc """
Adds a plain map item to the inventory (used for drops).
Returns {:ok, new_inventory, assigned_item} or {:error, :inventory_full}.
"""
def add_item(%__MODULE__{} = inv, %{} = item_map) when not is_struct(item_map) do
slot = next_free_slot(inv)
if slot < 0 do
{:error, :inventory_full}
else
# Convert map to item with assigned position
assigned_item = Map.put(item_map, :position, slot)
new_items = Map.put(inv.items, slot, assigned_item)
{:ok, %{inv | items: new_items}, assigned_item}
end
end
@doc """
Adds an item from the database (preserves position).
"""
@@ -391,4 +419,103 @@ defmodule Odinsea.Game.Inventory do
end
def equipped_items(%__MODULE__{}), do: []
@doc """
Gets the inventory type based on item ID.
"""
def get_type_by_item_id(item_id) do
InventoryType.from_item_id(item_id)
end
@doc """
Checks if inventory has at least the specified quantity of an item.
"""
def has_item_count(%__MODULE__{} = inv, item_id, quantity) do
count_by_id(inv, item_id) >= quantity
end
@doc """
Checks if there's at least one free slot in the inventory.
"""
def has_free_slot(%__MODULE__{} = inv) do
next_free_slot(inv) >= 0
end
@doc """
Checks if inventory can hold the specified quantity of an item.
For stackable items, checks if there's room to stack or a free slot.
"""
def can_hold_quantity(%__MODULE__{} = inv, item_id, quantity) do
# Find existing item to check stack space
existing = find_by_id(inv, item_id)
slot_max = InventoryType.slot_limit(inv.type)
if existing do
# Check if we can stack
space_in_stack = slot_max - existing.quantity
remaining = quantity - space_in_stack
if remaining <= 0 do
true
else
# Need additional slots
free_slots = count_free_slots(inv)
slots_needed = div(remaining, slot_max) + if rem(remaining, slot_max) > 0, do: 1, else: 0
free_slots >= slots_needed
end
else
# Need new slot(s)
free_slots = count_free_slots(inv)
slots_needed = div(quantity, slot_max) + if rem(quantity, slot_max) > 0, do: 1, else: 0
free_slots >= slots_needed
end
end
@doc """
Removes items by item ID.
Returns {:ok, new_inventory, removed_count} or {:error, reason}.
"""
def remove_by_id(%__MODULE__{} = inv, item_id, quantity) do
items_with_id =
inv.items
|> Map.values()
|> Enum.filter(fn item -> item.item_id == item_id end)
|> Enum.sort_by(fn item -> item.position end)
total_available = Enum.map(items_with_id, fn i -> i.quantity end) |> Enum.sum()
if total_available < quantity do
{:error, :insufficient_quantity}
else
{new_items, removed} = do_remove_by_id(inv.items, items_with_id, quantity, 0)
{:ok, %{inv | items: new_items}, removed}
end
end
defp do_remove_by_id(items, _items_to_remove, 0, removed), do: {items, removed}
defp do_remove_by_id(items, [], _quantity, removed), do: {items, removed}
defp do_remove_by_id(items, [item | rest], quantity, removed) do
if quantity <= 0 do
{items, removed}
else
to_remove = min(item.quantity, quantity)
new_quantity = item.quantity - to_remove
new_items = if new_quantity <= 0 do
Map.delete(items, item.position)
else
Map.put(items, item.position, %{item | quantity: new_quantity})
end
do_remove_by_id(new_items, rest, quantity - to_remove, removed + to_remove)
end
end
@doc """
Counts free slots in the inventory.
"""
def count_free_slots(%__MODULE__{} = inv) do
used_slots = map_size(inv.items)
inv.slot_limit - used_slots
end
end