port over some more
This commit is contained in:
247
lib/odinsea/channel/handler/pickup.ex
Normal file
247
lib/odinsea/channel/handler/pickup.ex
Normal file
@@ -0,0 +1,247 @@
|
||||
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
|
||||
Reference in New Issue
Block a user