267 lines
7.9 KiB
Elixir
267 lines
7.9 KiB
Elixir
defmodule Odinsea.Channel.Handler.Chat do
|
|
@moduledoc """
|
|
Handles chat packets (general, party, whisper, messenger).
|
|
Ported from src/handling/channel/handler/ChatHandler.java
|
|
"""
|
|
|
|
require Logger
|
|
|
|
alias Odinsea.Net.Packet.In
|
|
alias Odinsea.Channel.Packets
|
|
alias Odinsea.Game.Character
|
|
|
|
@max_chat_length 80
|
|
@max_staff_chat_length 512
|
|
|
|
@doc """
|
|
Handles general chat (CP_USER_CHAT).
|
|
Ported from ChatHandler.GeneralChat()
|
|
"""
|
|
def handle_general_chat(packet, client_state) do
|
|
with {:ok, character_pid} <- get_character(client_state),
|
|
{:ok, character} <- Character.get_state(character_pid),
|
|
{:ok, map_pid} <- get_map_pid(character.map_id, client_state.channel_id) do
|
|
# Decode packet
|
|
{tick, packet} = In.decode_int(packet)
|
|
{message, packet} = In.decode_string(packet)
|
|
{only_balloon, _packet} = In.decode_byte(packet)
|
|
|
|
# Validate message
|
|
cond do
|
|
String.length(message) == 0 ->
|
|
{:ok, client_state}
|
|
|
|
String.length(message) >= @max_chat_length ->
|
|
Logger.warning("Chat message too long from character #{character.id}")
|
|
{:ok, client_state}
|
|
|
|
true ->
|
|
# TODO: Process commands (CommandProcessor.processCommand)
|
|
# TODO: Check if muted
|
|
# TODO: Anti-spam checks
|
|
|
|
# Broadcast chat to map
|
|
chat_packet = Packets.user_chat(character.id, message, false, only_balloon == 1)
|
|
|
|
Odinsea.Game.Map.broadcast(map_pid, chat_packet)
|
|
|
|
# Log chat
|
|
Logger.info(
|
|
"Chat [#{character.name}] (Map #{character.map_id}): #{message}"
|
|
)
|
|
|
|
{:ok, client_state}
|
|
end
|
|
else
|
|
{:error, reason} ->
|
|
Logger.warning("General chat failed: #{inspect(reason)}")
|
|
{:ok, client_state}
|
|
end
|
|
end
|
|
|
|
@doc """
|
|
Handles party chat (CP_PARTY_CHAT).
|
|
Ported from ChatHandler.PartyChat()
|
|
|
|
Chat types:
|
|
- 0: Buddy
|
|
- 1: Party
|
|
- 2: Guild
|
|
- 3: Alliance
|
|
- 4: Expedition
|
|
"""
|
|
def handle_party_chat(packet, client_state) do
|
|
with {:ok, character_pid} <- get_character(client_state),
|
|
{:ok, character} <- Character.get_state(character_pid) do
|
|
# Decode packet
|
|
{chat_type, packet} = In.decode_byte(packet)
|
|
{num_recipients, packet} = In.decode_byte(packet)
|
|
|
|
# Validate recipients count
|
|
if num_recipients < 1 or num_recipients > 6 do
|
|
{:ok, client_state}
|
|
else
|
|
# Read recipient IDs
|
|
{recipients, packet} = decode_recipients(packet, num_recipients, [])
|
|
{message, _packet} = In.decode_string(packet)
|
|
|
|
# Validate message
|
|
if String.length(message) == 0 do
|
|
{:ok, client_state}
|
|
else
|
|
# TODO: Process commands
|
|
# TODO: Check if muted
|
|
|
|
# Route based on chat type
|
|
route_party_chat(chat_type, character, recipients, message)
|
|
|
|
# Log chat
|
|
chat_type_name = get_chat_type_name(chat_type)
|
|
|
|
Logger.info(
|
|
"Chat [#{character.name}] (#{chat_type_name}): #{message}"
|
|
)
|
|
|
|
{:ok, client_state}
|
|
end
|
|
end
|
|
else
|
|
{:error, reason} ->
|
|
Logger.warning("Party chat failed: #{inspect(reason)}")
|
|
{:ok, client_state}
|
|
end
|
|
end
|
|
|
|
@doc """
|
|
Handles whisper/find commands (CP_WHISPER).
|
|
Ported from ChatHandler.WhisperFind()
|
|
"""
|
|
def handle_whisper(packet, client_state) do
|
|
with {:ok, character_pid} <- get_character(client_state),
|
|
{:ok, character} <- Character.get_state(character_pid) do
|
|
# Decode packet
|
|
{mode, packet} = In.decode_byte(packet)
|
|
{_tick, packet} = In.decode_int(packet)
|
|
|
|
case mode do
|
|
# Find player (mode 5 or 68)
|
|
mode when mode in [5, 68] ->
|
|
{recipient, _packet} = In.decode_string(packet)
|
|
handle_find_player(recipient, character, client_state)
|
|
|
|
# Whisper (mode 6)
|
|
6 ->
|
|
{recipient, packet} = In.decode_string(packet)
|
|
{message, _packet} = In.decode_string(packet)
|
|
handle_whisper_message(recipient, message, character, client_state)
|
|
|
|
_ ->
|
|
Logger.warning("Unknown whisper mode: #{mode}")
|
|
{:ok, client_state}
|
|
end
|
|
else
|
|
{:error, reason} ->
|
|
Logger.warning("Whisper failed: #{inspect(reason)}")
|
|
{:ok, client_state}
|
|
end
|
|
end
|
|
|
|
# ============================================================================
|
|
# Private 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 get_map_pid(map_id, channel_id) do
|
|
case Registry.lookup(Odinsea.MapRegistry, {map_id, channel_id}) do
|
|
[{pid, _}] ->
|
|
{:ok, pid}
|
|
|
|
[] ->
|
|
# Map not loaded yet - load it
|
|
case DynamicSupervisor.start_child(
|
|
Odinsea.MapSupervisor,
|
|
{Odinsea.Game.Map, {map_id, channel_id}}
|
|
) do
|
|
{:ok, pid} -> {:ok, pid}
|
|
{:error, {:already_started, pid}} -> {:ok, pid}
|
|
error -> error
|
|
end
|
|
end
|
|
end
|
|
|
|
defp decode_recipients(packet, 0, acc), do: {Enum.reverse(acc), packet}
|
|
|
|
defp decode_recipients(packet, count, acc) do
|
|
{recipient_id, packet} = In.decode_int(packet)
|
|
decode_recipients(packet, count - 1, [recipient_id | acc])
|
|
end
|
|
|
|
defp route_party_chat(chat_type, character, recipients, message) do
|
|
case chat_type do
|
|
0 ->
|
|
# Buddy chat
|
|
Logger.debug("Buddy chat from #{character.name} to #{inspect(recipients)}: #{message}")
|
|
# TODO: Implement World.Buddy.buddyChat
|
|
|
|
1 ->
|
|
# Party chat
|
|
Logger.debug("Party chat from #{character.name}: #{message}")
|
|
# TODO: Implement World.Party.partyChat
|
|
|
|
2 ->
|
|
# Guild chat
|
|
Logger.debug("Guild chat from #{character.name}: #{message}")
|
|
# TODO: Implement World.Guild.guildChat
|
|
|
|
3 ->
|
|
# Alliance chat
|
|
Logger.debug("Alliance chat from #{character.name}: #{message}")
|
|
# TODO: Implement World.Alliance.allianceChat
|
|
|
|
4 ->
|
|
# Expedition chat
|
|
Logger.debug("Expedition chat from #{character.name}: #{message}")
|
|
# TODO: Implement World.Party.expedChat
|
|
|
|
_ ->
|
|
Logger.warning("Unknown party chat type: #{chat_type}")
|
|
end
|
|
end
|
|
|
|
defp get_chat_type_name(0), do: "Buddy"
|
|
defp get_chat_type_name(1), do: "Party"
|
|
defp get_chat_type_name(2), do: "Guild"
|
|
defp get_chat_type_name(3), do: "Alliance"
|
|
defp get_chat_type_name(4), do: "Expedition"
|
|
defp get_chat_type_name(_), do: "Unknown"
|
|
|
|
defp handle_find_player(recipient, character, client_state) do
|
|
# TODO: Implement player search across channels
|
|
# For now, just search locally
|
|
case Odinsea.Channel.Players.find_by_name(client_state.channel_id, recipient) do
|
|
{:ok, _target_character} ->
|
|
# Send find reply with map
|
|
# TODO: Send packet
|
|
Logger.debug("Found player #{recipient} on current channel")
|
|
{:ok, client_state}
|
|
|
|
{:error, :not_found} ->
|
|
# Search other channels
|
|
Logger.debug("Player #{recipient} not found")
|
|
# TODO: Send "player not found" packet
|
|
{:ok, client_state}
|
|
end
|
|
end
|
|
|
|
defp handle_whisper_message(recipient, message, character, client_state) do
|
|
# TODO: Check if muted
|
|
# TODO: Check blacklist
|
|
|
|
# Validate message
|
|
if String.length(message) == 0 do
|
|
{:ok, client_state}
|
|
else
|
|
# TODO: Find recipient across channels and send whisper
|
|
Logger.info("Whisper [#{character.name} -> #{recipient}]: #{message}")
|
|
|
|
# For now, just log
|
|
# TODO: Send whisper packet to recipient
|
|
# TODO: Send whisper reply packet to sender
|
|
|
|
{:ok, client_state}
|
|
end
|
|
end
|
|
end
|