defmodule Odinsea.Channel.Handler.Pickup do @moduledoc """ Handles drop pickup from the map (item and meso drops). Ported from src/handling/channel/handler/InventoryHandler.java This handler processes CP_ItemPickup (0x10C) packets when a player attempts to pick up a drop from the map. """ require Logger alias Odinsea.Net.Packet.{In, Out} alias Odinsea.Net.Opcodes alias Odinsea.Game.{Character, Drop, Map} alias Odinsea.Channel.Packets @doc """ Handles item pickup from map (CP_ItemPickup). Packet structure: - tick (4 bytes): Client tick count - oid (4 bytes): Object ID of the drop on the map Ported from InventoryHandler.handlePickup() """ def handle_item_pickup(packet, client_state) do with {:ok, character_pid} <- get_character(client_state), {:ok, character} <- Character.get_state(character_pid) do # Decode packet {_tick, packet} = In.decode_int(packet) {drop_oid, _packet} = In.decode_int(packet) Logger.debug("Item pickup attempt: character=#{character.name}, drop_oid=#{drop_oid}") # Attempt to pick up the drop case attempt_pickup_drop(character, character_pid, drop_oid, client_state.channel_id) do {:ok, :meso, amount} -> Logger.debug("Picked up meso: #{amount}") # Send pickup success response send_pickup_result(client_state, 0, 0) {:ok, client_state} {:ok, :item, item} -> Logger.debug("Picked up item: #{item.item_id}") # Send pickup success response send_pickup_result(client_state, 0, 0) {:ok, client_state} {:error, reason} -> Logger.debug("Pickup failed: #{reason}") # Send failure response (enable actions to unblock client) send_enable_actions(client_state) {:ok, client_state} end else {:error, reason} -> Logger.warning("Item pickup failed: #{inspect(reason)}") send_enable_actions(client_state) {:ok, client_state} end end @doc """ Handles pet item pickup request (CP_PetDropPickUpRequest). Similar to player pickup but initiated by pet movement. """ def handle_pet_item_pickup(packet, client_state) do with {:ok, character_pid} <- get_character(client_state), {:ok, character} <- Character.get_state(character_pid) do # Decode packet {_tick, packet} = In.decode_int(packet) {pet_slot, packet} = In.decode_byte(packet) {drop_oid, _packet} = In.decode_int(packet) Logger.debug("Pet item pickup attempt: character=#{character.name}, pet_slot=#{pet_slot}, drop_oid=#{drop_oid}") # Attempt to pick up the drop (pets have same rules but different animation) case attempt_pickup_drop(character, character_pid, drop_oid, client_state.channel_id, pet_slot) do {:ok, :meso, amount} -> Logger.debug("Pet picked up meso: #{amount}") {:ok, client_state} {:ok, :item, item} -> Logger.debug("Pet picked up item: #{item.item_id}") {:ok, client_state} {:error, _reason} -> {:ok, client_state} end else {:error, _reason} -> {:ok, client_state} end end # ============================================================================ # Private Helper Functions # ============================================================================ defp attempt_pickup_drop(character, character_pid, drop_oid, channel_id, pet_slot \\ nil) do # Call Map.pickup_drop to atomically attempt pickup case Map.pickup_drop(character.map_id, channel_id, drop_oid, character.id) do {:ok, drop} -> # Successfully claimed the drop, now process it process_pickup(character, character_pid, drop, channel_id, pet_slot) {:error, reason} -> {:error, reason} end end defp process_pickup(character, character_pid, %Drop{} = drop, channel_id, pet_slot) do cond do Drop.meso?(drop) -> process_meso_pickup(character, character_pid, drop, channel_id, pet_slot) Drop.item?(drop) -> process_item_pickup(character, character_pid, drop, channel_id, pet_slot) true -> {:error, :invalid_drop} end end defp process_meso_pickup(character, character_pid, %Drop{meso: amount} = drop, channel_id, pet_slot) do # Add meso to character case Character.gain_meso(character_pid, amount, true) do {:ok, _new_meso} -> # Broadcast pickup animation to all players on map broadcast_pickup(character.map_id, channel_id, drop.oid, character.id, pet_slot) # Show meso gain in chat (optional) # send_meso_gain_message(client_state, amount) {:ok, :meso, amount} {:error, reason} -> Logger.warning("Failed to add meso: #{reason}") {:error, :gain_meso_failed} end end defp process_item_pickup(character, character_pid, %Drop{} = drop, channel_id, pet_slot) do # Check inventory space inventory_type = get_inventory_type(drop.item_id) case Character.check_inventory_space(character_pid, inventory_type, drop.quantity) do {:ok, _slot} -> # Add item to inventory item_to_add = drop.item || create_item_from_drop(drop) case Character.add_item_from_drop(character_pid, item_to_add) do {:ok, added_item} -> # Broadcast pickup animation broadcast_pickup(character.map_id, channel_id, drop.oid, character.id, pet_slot) {:ok, :item, added_item} {:error, reason} -> Logger.warning("Failed to add item to inventory: #{reason}") # Item couldn't be added - drop would normally be returned to map # but for simplicity we just fail {:error, :add_item_failed} end {:error, :inventory_full} -> # Send inventory full message to client {:error, :inventory_full} {:error, reason} -> {:error, reason} end end defp create_item_from_drop(%Drop{} = drop) do # Create a basic item struct from drop data %{ item_id: drop.item_id, quantity: drop.quantity, position: 0 # Will be assigned by inventory } end defp get_inventory_type(item_id) do # Determine inventory type from item ID type_prefix = div(item_id, 1_000_000) case type_prefix do 1 -> :equip 2 -> :use 3 -> :setup 4 -> :etc 5 -> :cash _ -> :etc end end defp broadcast_pickup(map_id, channel_id, drop_oid, character_id, nil) do # Player pickup - animation type 2 remove_packet = Packets.remove_drop(drop_oid, 2, character_id) Map.broadcast(map_id, channel_id, remove_packet) end defp broadcast_pickup(map_id, channel_id, drop_oid, character_id, pet_slot) do # Pet pickup - animation type 5 remove_packet = Packets.remove_drop(drop_oid, 5, character_id, pet_slot) Map.broadcast(map_id, channel_id, remove_packet) end defp send_pickup_result(client_state, _result, _item_id) do # Send inventory update or status packet # For now, just enable actions send_enable_actions(client_state) end defp send_enable_actions(client_state) do # Send enable actions packet to allow further client actions enable_packet = Packets.enable_actions() send_packet(client_state, enable_packet) end defp send_packet(client_state, data) when is_pid(client_state) do send(client_state, {:send_packet, data}) end defp send_packet(client_state, data) do if client_state.client_pid do send(client_state.client_pid, {:send_packet, data}) end end defp get_character(client_state) do case client_state.character_id do nil -> {:error, :no_character} character_id -> case Registry.lookup(Odinsea.CharacterRegistry, character_id) do [{pid, _}] -> {:ok, pid} [] -> {:error, :character_not_found} end end end end