This commit is contained in:
ra
2026-02-14 19:36:59 -07:00
parent f5b8aeb39d
commit bbd205ecbe
19 changed files with 5191 additions and 554 deletions

View File

@@ -11,6 +11,7 @@ defmodule Odinsea.Game.Character do
alias Odinsea.Database.Schema.Character, as: CharacterDB
alias Odinsea.Game.Map, as: GameMap
alias Odinsea.Game.{Inventory, InventoryType}
alias Odinsea.Net.Packet.Out
# ============================================================================
@@ -231,6 +232,52 @@ defmodule Odinsea.Game.Character do
GenServer.stop(via_tuple(character_id), :normal)
end
# ============================================================================
# Inventory API
# ============================================================================
@doc """
Gets a specific inventory.
"""
def get_inventory(character_id, inv_type) do
GenServer.call(via_tuple(character_id), {:get_inventory, inv_type})
end
@doc """
Gets an item from a specific inventory slot.
"""
def get_item(character_id, inv_type, position) do
GenServer.call(via_tuple(character_id), {:get_item, inv_type, position})
end
@doc """
Moves an item within or between inventories.
"""
def move_item(character_id, inv_type, src_slot, dst_slot, slot_max \\ 100) do
GenServer.call(via_tuple(character_id), {:move_item, inv_type, src_slot, dst_slot, slot_max})
end
@doc """
Equips an item (moves from EQUIP inventory to EQUIPPED).
"""
def equip_item(character_id, src_slot, dst_slot) do
GenServer.call(via_tuple(character_id), {:equip_item, src_slot, dst_slot})
end
@doc """
Unequips an item (moves from EQUIPPED to EQUIP inventory).
"""
def unequip_item(character_id, src_slot, dst_slot) do
GenServer.call(via_tuple(character_id), {:unequip_item, src_slot, dst_slot})
end
@doc """
Drops an item from inventory.
"""
def drop_item(character_id, inv_type, position, quantity \\ 1) do
GenServer.call(via_tuple(character_id), {:drop_item, inv_type, position, quantity})
end
# ============================================================================
# GenServer Callbacks
# ============================================================================
@@ -286,6 +333,93 @@ defmodule Odinsea.Game.Character do
{:reply, result, state}
end
@impl true
def handle_call({:get_inventory, inv_type}, _from, state) do
inventory = Map.get(state.inventories, inv_type, Inventory.new(inv_type))
{:reply, inventory, state}
end
@impl true
def handle_call({:get_item, inv_type, position}, _from, state) do
inventory = Map.get(state.inventories, inv_type, Inventory.new(inv_type))
item = Inventory.get_item(inventory, position)
{:reply, item, state}
end
@impl true
def handle_call({:move_item, inv_type, src_slot, dst_slot, slot_max}, _from, state) do
inventory = Map.get(state.inventories, inv_type, Inventory.new(inv_type))
case Inventory.move(inventory, src_slot, dst_slot, slot_max) do
{:ok, new_inventory} ->
new_inventories = Map.put(state.inventories, inv_type, new_inventory)
new_state = %{state | inventories: new_inventories, updated_at: DateTime.utc_now()}
{:reply, :ok, new_state}
{:error, reason} ->
{:reply, {:error, reason}, state}
end
end
@impl true
def handle_call({:equip_item, src_slot, dst_slot}, _from, state) do
# Move from EQUIP to EQUIPPED
equip_inv = Map.get(state.inventories, :equip, Inventory.new(:equip))
equipped_inv = Map.get(state.inventories, :equipped, Inventory.new(:equipped))
case Inventory.move(equip_inv, src_slot, dst_slot, 1) do
{:ok, new_equip_inv} ->
new_inventories =
state.inventories
|> Map.put(:equip, new_equip_inv)
|> Map.put(:equipped, equipped_inv)
# TODO: Recalculate stats based on new equipment
new_state = %{state | inventories: new_inventories, updated_at: DateTime.utc_now()}
{:reply, :ok, new_state}
{:error, reason} ->
{:reply, {:error, reason}, state}
end
end
@impl true
def handle_call({:unequip_item, src_slot, dst_slot}, _from, state) do
# Move from EQUIPPED to EQUIP
equipped_inv = Map.get(state.inventories, :equipped, Inventory.new(:equipped))
equip_inv = Map.get(state.inventories, :equip, Inventory.new(:equip))
case Inventory.move(equipped_inv, src_slot, dst_slot, 1) do
{:ok, new_equipped_inv} ->
new_inventories =
state.inventories
|> Map.put(:equipped, new_equipped_inv)
|> Map.put(:equip, equip_inv)
# TODO: Recalculate stats based on removed equipment
new_state = %{state | inventories: new_inventories, updated_at: DateTime.utc_now()}
{:reply, :ok, new_state}
{:error, reason} ->
{:reply, {:error, reason}, state}
end
end
@impl true
def handle_call({:drop_item, inv_type, position, quantity}, _from, state) do
inventory = Map.get(state.inventories, inv_type, Inventory.new(inv_type))
case Inventory.drop(inventory, position, quantity) do
{:ok, dropped_item, new_inventory} ->
new_inventories = Map.put(state.inventories, inv_type, new_inventory)
new_state = %{state | inventories: new_inventories, updated_at: DateTime.utc_now()}
{:reply, {:ok, dropped_item}, new_state}
{:error, reason} ->
{:reply, {:error, reason}, state}
end
end
@impl true
def handle_cast({:update_position, position}, state) do
new_state = %{
@@ -361,6 +495,9 @@ defmodule Odinsea.Game.Character do
sp_list when is_list(sp_list) -> sp_list
end
# Load inventories from database
inventories = load_inventories(db_char.id)
%State{
character_id: db_char.id,
account_id: db_char.account_id,
@@ -383,7 +520,7 @@ defmodule Odinsea.Game.Character do
remaining_ap: db_char.remaining_ap,
remaining_sp: remaining_sp,
client_pid: client_pid,
inventories: %{},
inventories: inventories,
skills: %{},
buffs: [],
pets: [],
@@ -392,6 +529,30 @@ defmodule Odinsea.Game.Character do
}
end
defp load_inventories(character_id) do
# Initialize empty inventories for all types
base_inventories =
InventoryType.all_types()
|> Map.new(fn type -> {type, Inventory.new(type)} end)
# Load items from database
case Odinsea.Database.Context.load_character_inventory(character_id) do
nil ->
base_inventories
items_by_type ->
# Add items to appropriate inventories
Enum.reduce(items_by_type, base_inventories, fn {type, items}, acc ->
inventory =
Enum.reduce(items, Inventory.new(type), fn item, inv ->
Inventory.add_from_db(inv, item)
end)
Map.put(acc, type, inventory)
end)
end
end
defp parse_sp_string(sp_str) do
sp_str
|> String.split(",")
@@ -429,6 +590,12 @@ defmodule Odinsea.Game.Character do
remaining_sp: sp_string
}
Odinsea.Database.Context.update_character(state.character_id, attrs)
# Save character base data
result = Odinsea.Database.Context.update_character(state.character_id, attrs)
# Save inventories
Odinsea.Database.Context.save_character_inventory(state.character_id, state.inventories)
result
end
end