Files
odinsea-elixir/lib/odinsea/channel/handler/pickup.ex
2026-02-14 23:58:01 -07:00

248 lines
7.9 KiB
Elixir

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