kimi gone wild
This commit is contained in:
418
lib/odinsea/channel/handler/buddy.ex
Normal file
418
lib/odinsea/channel/handler/buddy.ex
Normal file
@@ -0,0 +1,418 @@
|
||||
defmodule Odinsea.Channel.Handler.Buddy do
|
||||
@moduledoc """
|
||||
Handles buddy list operations.
|
||||
Ported from src/handling/channel/handler/BuddyListHandler.java
|
||||
|
||||
Manages buddy list add, remove, and accept operations.
|
||||
"""
|
||||
|
||||
require Logger
|
||||
|
||||
alias Odinsea.Net.Packet.In
|
||||
alias Odinsea.Channel.Packets
|
||||
alias Odinsea.Game.Character
|
||||
alias Odinsea.Database.Context
|
||||
|
||||
@max_buddy_list 100
|
||||
@default_capacity 20
|
||||
|
||||
@doc """
|
||||
Handles buddy list operations (CP_BUDDYLIST_MODIFY).
|
||||
Ported from BuddyListHandler.BuddyOperation()
|
||||
|
||||
Mode:
|
||||
- 1: Add buddy
|
||||
- 2: Accept buddy
|
||||
- 3: Delete buddy
|
||||
"""
|
||||
def handle_buddy_operation(packet, client_state) do
|
||||
with {:ok, character_pid} <- get_character(client_state),
|
||||
{:ok, character} <- Character.get_state(character_pid) do
|
||||
|
||||
{mode, packet} = In.decode_byte(packet)
|
||||
|
||||
case mode do
|
||||
1 -> handle_add_buddy(packet, character, client_state)
|
||||
2 -> handle_accept_buddy(packet, character, client_state)
|
||||
3 -> handle_delete_buddy(packet, character, client_state)
|
||||
_ ->
|
||||
Logger.warning("Unknown buddy operation mode: #{mode}")
|
||||
{:ok, client_state}
|
||||
end
|
||||
else
|
||||
{:error, reason} ->
|
||||
Logger.warning("Buddy operation failed: #{inspect(reason)}")
|
||||
{:ok, client_state}
|
||||
end
|
||||
end
|
||||
|
||||
# ============================================================================
|
||||
# Add Buddy Handler
|
||||
# ============================================================================
|
||||
|
||||
defp handle_add_buddy(packet, character, client_state) do
|
||||
{add_name, packet} = In.decode_string(packet)
|
||||
{group_name, _packet} = In.decode_string(packet)
|
||||
|
||||
# Validate inputs
|
||||
if String.length(add_name) > 13 || String.length(group_name) > 16 do
|
||||
{:ok, client_state}
|
||||
else
|
||||
# Check if already in buddy list
|
||||
existing = find_buddy(character.buddies, add_name)
|
||||
|
||||
cond do
|
||||
existing && existing.group == group_name ->
|
||||
# Already in list with same group
|
||||
send_buddy_message(client_state, 11)
|
||||
|
||||
existing && !existing.pending ->
|
||||
# Update group
|
||||
updated_buddies = update_buddy_group(character.buddies, add_name, group_name)
|
||||
Character.update_buddies(character.id, updated_buddies)
|
||||
|
||||
# Send updated buddy list
|
||||
buddy_list_packet = Packets.update_buddylist(updated_buddies, 10)
|
||||
send_packet(client_state, buddy_list_packet)
|
||||
|
||||
length(character.buddies) >= @max_buddy_list ->
|
||||
# Buddy list full
|
||||
send_buddy_message(client_state, 11)
|
||||
|
||||
true ->
|
||||
# Try to find and add buddy
|
||||
add_buddy_to_list(add_name, group_name, character, client_state)
|
||||
end
|
||||
|
||||
{:ok, client_state}
|
||||
end
|
||||
end
|
||||
|
||||
defp add_buddy_to_list(add_name, group_name, character, client_state) do
|
||||
# Try to find character on current channel
|
||||
case Odinsea.Channel.Players.find_by_name(client_state.channel_id, add_name) do
|
||||
{:ok, target_character} ->
|
||||
# Check if can add (not GM hiding, not blacklisted)
|
||||
if can_add_buddy?(character, target_character) do
|
||||
# Check target's buddy list capacity
|
||||
if length(target_character.buddies) >= @default_capacity do
|
||||
send_buddy_message(client_state, 12)
|
||||
else
|
||||
# Send buddy request to target
|
||||
send_buddy_request(target_character, character)
|
||||
|
||||
# Add pending buddy to our list
|
||||
buddy = create_buddy_entry(target_character, group_name, -1, true)
|
||||
updated_buddies = character.buddies ++ [buddy]
|
||||
Character.update_buddies(character.id, updated_buddies)
|
||||
|
||||
# Send updated buddy list
|
||||
buddy_list_packet = Packets.update_buddylist(updated_buddies, 10)
|
||||
send_packet(client_state, buddy_list_packet)
|
||||
end
|
||||
else
|
||||
send_buddy_message(client_state, 15)
|
||||
end
|
||||
|
||||
{:error, :not_found} ->
|
||||
# Try to find in database
|
||||
case Context.get_character_by_name(add_name) do
|
||||
nil ->
|
||||
send_buddy_message(client_state, 15)
|
||||
|
||||
target_db ->
|
||||
# Check if target can accept buddy
|
||||
if target_db.gm_level < 3 do
|
||||
# Check buddy capacity in database
|
||||
case get_buddy_count_from_db(target_db.id) do
|
||||
{:ok, count} when count >= @default_capacity ->
|
||||
send_buddy_message(client_state, 12)
|
||||
|
||||
_ ->
|
||||
# Add pending to database
|
||||
insert_pending_buddy(target_db.id, character.id, group_name)
|
||||
|
||||
# Add pending buddy to our list
|
||||
buddy = create_buddy_entry_from_db(target_db, group_name, true)
|
||||
updated_buddies = character.buddies ++ [buddy]
|
||||
Character.update_buddies(character.id, updated_buddies)
|
||||
|
||||
# Send updated buddy list
|
||||
buddy_list_packet = Packets.update_buddylist(updated_buddies, 10)
|
||||
send_packet(client_state, buddy_list_packet)
|
||||
end
|
||||
else
|
||||
send_buddy_message(client_state, 15)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
# ============================================================================
|
||||
# Accept Buddy Handler
|
||||
# ============================================================================
|
||||
|
||||
defp handle_accept_buddy(packet, character, client_state) do
|
||||
{other_cid, _packet} = In.decode_int(packet)
|
||||
|
||||
# Find pending buddy
|
||||
buddy = Enum.find(character.buddies, fn b ->
|
||||
b.character_id == other_cid && b.pending
|
||||
end)
|
||||
|
||||
if buddy && length(character.buddies) < @max_buddy_list do
|
||||
# Accept the buddy
|
||||
updated_buddy = %{buddy | pending: false, visible: true, group: "ETC"}
|
||||
|
||||
# Update buddy in list
|
||||
updated_buddies = Enum.map(character.buddies, fn b ->
|
||||
if b.character_id == other_cid do
|
||||
updated_buddy
|
||||
else
|
||||
b
|
||||
end
|
||||
end)
|
||||
|
||||
Character.update_buddies(character.id, updated_buddies)
|
||||
|
||||
# Try to find channel
|
||||
channel = find_buddy_channel(other_cid)
|
||||
|
||||
# Send updated buddy list
|
||||
buddy_list_packet = Packets.update_buddylist(updated_buddies, 10)
|
||||
send_packet(client_state, buddy_list_packet)
|
||||
|
||||
# Notify other player if online
|
||||
if channel > 0 do
|
||||
notify_buddy_added(other_cid, character, "ETC")
|
||||
end
|
||||
|
||||
# Update in database
|
||||
accept_buddy_in_db(character.id, other_cid)
|
||||
else
|
||||
send_buddy_message(client_state, 11)
|
||||
end
|
||||
|
||||
{:ok, client_state}
|
||||
end
|
||||
|
||||
# ============================================================================
|
||||
# Delete Buddy Handler
|
||||
# ============================================================================
|
||||
|
||||
defp handle_delete_buddy(packet, character, client_state) do
|
||||
{other_cid, _packet} = In.decode_int(packet)
|
||||
|
||||
# Find buddy
|
||||
buddy = Enum.find(character.buddies, fn b -> b.character_id == other_cid end)
|
||||
|
||||
if buddy do
|
||||
# Notify other player if online and visible
|
||||
if buddy.visible do
|
||||
channel = find_buddy_channel(other_cid)
|
||||
if channel > 0 do
|
||||
notify_buddy_removed(other_cid, character.id)
|
||||
end
|
||||
end
|
||||
|
||||
# Remove from our list
|
||||
updated_buddies = Enum.reject(character.buddies, fn b ->
|
||||
b.character_id == other_cid
|
||||
end)
|
||||
|
||||
Character.update_buddies(character.id, updated_buddies)
|
||||
|
||||
# Send updated buddy list
|
||||
buddy_list_packet = Packets.update_buddylist(updated_buddies, 18)
|
||||
send_packet(client_state, buddy_list_packet)
|
||||
|
||||
# Remove from database
|
||||
remove_buddy_from_db(character.id, other_cid)
|
||||
end
|
||||
|
||||
{:ok, client_state}
|
||||
end
|
||||
|
||||
# ============================================================================
|
||||
# Helper Functions
|
||||
# ============================================================================
|
||||
|
||||
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
|
||||
|
||||
defp find_buddy(buddies, name) do
|
||||
name_lower = String.downcase(name)
|
||||
Enum.find(buddies, fn b ->
|
||||
String.downcase(b.name) == name_lower
|
||||
end)
|
||||
end
|
||||
|
||||
defp update_buddy_group(buddies, name, group_name) do
|
||||
Enum.map(buddies, fn b ->
|
||||
if String.downcase(b.name) == String.downcase(name) do
|
||||
%{b | group: group_name}
|
||||
else
|
||||
b
|
||||
end
|
||||
end)
|
||||
end
|
||||
|
||||
defp can_add_buddy?(character, target) do
|
||||
# Check if target is GM hiding
|
||||
if target.gm? && !character.gm? do
|
||||
false
|
||||
else
|
||||
# Check blacklist
|
||||
target_character =
|
||||
case Registry.lookup(Odinsea.CharacterRegistry, target.id) do
|
||||
[{pid, _}] ->
|
||||
case Character.get_state(pid) do
|
||||
{:ok, state} -> state
|
||||
_ -> nil
|
||||
end
|
||||
[] -> nil
|
||||
end
|
||||
|
||||
if target_character do
|
||||
not Enum.member?(target_character.blacklist, String.downcase(character.name))
|
||||
else
|
||||
true
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
defp create_buddy_entry(character, group, channel, pending) do
|
||||
%{
|
||||
character_id: character.id,
|
||||
name: character.name,
|
||||
group: group,
|
||||
channel: channel,
|
||||
visible: !pending,
|
||||
pending: pending,
|
||||
level: character.level,
|
||||
job: character.job
|
||||
}
|
||||
end
|
||||
|
||||
defp create_buddy_entry_from_db(character, group, pending) do
|
||||
%{
|
||||
character_id: character.id,
|
||||
name: character.name,
|
||||
group: group,
|
||||
channel: -1,
|
||||
visible: !pending,
|
||||
pending: pending,
|
||||
level: character.level,
|
||||
job: character.job
|
||||
}
|
||||
end
|
||||
|
||||
defp send_buddy_request(target_character, from_character) do
|
||||
case Registry.lookup(Odinsea.CharacterRegistry, target_character.id) do
|
||||
[{pid, _}] ->
|
||||
request_packet = Packets.request_buddylist_add(
|
||||
from_character.id,
|
||||
from_character.name,
|
||||
from_character.level,
|
||||
from_character.job
|
||||
)
|
||||
send(pid, {:send_packet, request_packet})
|
||||
[] -> :ok
|
||||
end
|
||||
end
|
||||
|
||||
defp notify_buddy_added(target_id, from_character, group) do
|
||||
case Registry.lookup(Odinsea.CharacterRegistry, target_id) do
|
||||
[{pid, _}] ->
|
||||
# Add buddy entry for target
|
||||
buddy_entry = create_buddy_entry(from_character, group, 1, false)
|
||||
|
||||
# Update target's buddies
|
||||
case Character.get_state(pid) do
|
||||
{:ok, target_state} ->
|
||||
updated_buddies = target_state.buddies ++ [buddy_entry]
|
||||
Character.update_buddies(target_id, updated_buddies)
|
||||
|
||||
# Send update packet
|
||||
buddy_list_packet = Packets.update_buddylist(updated_buddies, 10)
|
||||
send(pid, {:send_packet, buddy_list_packet})
|
||||
_ -> :ok
|
||||
end
|
||||
[] -> :ok
|
||||
end
|
||||
end
|
||||
|
||||
defp notify_buddy_removed(target_id, remover_id) do
|
||||
case Registry.lookup(Odinsea.CharacterRegistry, target_id) do
|
||||
[{pid, _}] ->
|
||||
case Character.get_state(pid) do
|
||||
{:ok, target_state} ->
|
||||
updated_buddies = Enum.reject(target_state.buddies, fn b ->
|
||||
b.character_id == remover_id
|
||||
end)
|
||||
|
||||
Character.update_buddies(target_id, updated_buddies)
|
||||
|
||||
buddy_list_packet = Packets.update_buddylist(updated_buddies, 18)
|
||||
send(pid, {:send_packet, buddy_list_packet})
|
||||
_ -> :ok
|
||||
end
|
||||
[] -> :ok
|
||||
end
|
||||
end
|
||||
|
||||
defp find_buddy_channel(character_id) do
|
||||
# Try to find character on any channel
|
||||
# For now, just check current channel's registry
|
||||
case Registry.lookup(Odinsea.CharacterRegistry, character_id) do
|
||||
[{_pid, _}] -> 1 # Found, return channel
|
||||
[] -> -1 # Not found
|
||||
end
|
||||
end
|
||||
|
||||
defp send_buddy_message(client_state, code) do
|
||||
packet = Packets.buddylist_message(code)
|
||||
send_packet(client_state, packet)
|
||||
end
|
||||
|
||||
defp send_packet(client_state, packet) do
|
||||
if client_state.socket do
|
||||
:gen_tcp.send(client_state.socket, packet)
|
||||
end
|
||||
end
|
||||
|
||||
# ============================================================================
|
||||
# Database Functions (Stubs)
|
||||
# ============================================================================
|
||||
|
||||
defp get_buddy_count_from_db(character_id) do
|
||||
# TODO: Query buddies table for count
|
||||
{:ok, 0}
|
||||
end
|
||||
|
||||
defp insert_pending_buddy(target_id, character_id, group_name) do
|
||||
# TODO: Insert pending buddy into database
|
||||
Logger.debug("Insert pending buddy: target=#{target_id}, from=#{character_id}, group=#{group_name}")
|
||||
:ok
|
||||
end
|
||||
|
||||
defp accept_buddy_in_db(character_id, other_id) do
|
||||
# TODO: Update buddy status in database
|
||||
Logger.debug("Accept buddy in DB: #{character_id} <-> #{other_id}")
|
||||
:ok
|
||||
end
|
||||
|
||||
defp remove_buddy_from_db(character_id, other_id) do
|
||||
# TODO: Remove buddy from database
|
||||
Logger.debug("Remove buddy from DB: #{character_id} X #{other_id}")
|
||||
:ok
|
||||
end
|
||||
end
|
||||
Reference in New Issue
Block a user