248 lines
7.9 KiB
Elixir
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
|