Start repo, claude & kimi still vibing tho
This commit is contained in:
266
lib/odinsea/channel/handler/chat.ex
Normal file
266
lib/odinsea/channel/handler/chat.ex
Normal file
@@ -0,0 +1,266 @@
|
||||
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
|
||||
Reference in New Issue
Block a user