Files
odinsea-elixir/lib/odinsea/channel/client.ex
2026-02-14 19:36:59 -07:00

294 lines
7.8 KiB
Elixir

defmodule Odinsea.Channel.Client do
@moduledoc """
Client connection handler for game channel servers.
Manages the game session state.
"""
use GenServer, restart: :temporary
require Logger
alias Odinsea.Net.Packet.In
alias Odinsea.Net.Opcodes
alias Odinsea.Channel.Handler
defstruct [:socket, :ip, :channel_id, :state, :character_id]
def start_link({socket, channel_id}) do
GenServer.start_link(__MODULE__, {socket, channel_id})
end
@impl true
def init({socket, channel_id}) do
{:ok, {ip, _port}} = :inet.peername(socket)
ip_string = format_ip(ip)
Logger.info("Channel #{channel_id} client connected from #{ip_string}")
state = %__MODULE__{
socket: socket,
ip: ip_string,
channel_id: channel_id,
state: :connected,
character_id: nil
}
send(self(), :receive)
{:ok, state}
end
@impl true
def handle_info(:receive, %{socket: socket} = state) do
case :gen_tcp.recv(socket, 0, 30_000) do
{:ok, data} ->
new_state = handle_packet(data, state)
send(self(), :receive)
{:noreply, new_state}
{:error, :closed} ->
Logger.info("Channel #{state.channel_id} client disconnected: #{state.ip}")
{:stop, :normal, state}
{:error, reason} ->
Logger.warning("Channel client error: #{inspect(reason)}")
{:stop, :normal, state}
end
end
@impl true
def terminate(_reason, state) do
if state.socket do
:gen_tcp.close(state.socket)
end
:ok
end
defp handle_packet(data, state) do
packet = In.new(data)
case In.decode_short(packet) do
{opcode, packet} ->
Logger.debug("Channel #{state.channel_id} packet: opcode=0x#{Integer.to_string(opcode, 16)}")
dispatch_packet(opcode, packet, state)
:error ->
Logger.warning("Failed to read packet opcode")
state
end
end
defp dispatch_packet(opcode, packet, state) do
# Define opcodes for matching
cp_general_chat = Opcodes.cp_general_chat()
cp_party_chat = Opcodes.cp_party_chat()
cp_whisper = Opcodes.cp_whisper()
cp_move_player = Opcodes.cp_move_player()
cp_change_map = Opcodes.cp_change_map()
cp_change_keymap = Opcodes.cp_change_keymap()
cp_skill_macro = Opcodes.cp_skill_macro()
cp_close_range_attack = Opcodes.cp_close_range_attack()
cp_ranged_attack = Opcodes.cp_ranged_attack()
cp_magic_attack = Opcodes.cp_magic_attack()
cp_take_damage = Opcodes.cp_take_damage()
# Inventory opcodes
cp_item_move = Opcodes.cp_item_move()
cp_item_sort = Opcodes.cp_item_sort()
cp_item_gather = Opcodes.cp_item_gather()
cp_use_item = Opcodes.cp_use_item()
cp_use_return_scroll = Opcodes.cp_use_return_scroll()
cp_use_scroll = Opcodes.cp_use_upgrade_scroll()
cp_use_cash_item = Opcodes.cp_use_cash_item()
# NPC opcodes
cp_npc_move = Opcodes.cp_npc_move()
cp_npc_talk = Opcodes.cp_npc_talk()
cp_npc_talk_more = Opcodes.cp_npc_talk_more()
cp_npc_shop = Opcodes.cp_npc_shop()
cp_storage = Opcodes.cp_storage()
cp_quest_action = Opcodes.cp_quest_action()
cp_repair = Opcodes.cp_repair()
cp_repair_all = Opcodes.cp_repair_all()
cp_update_quest = Opcodes.cp_update_quest()
cp_use_item_quest = Opcodes.cp_use_item_quest()
cp_public_npc = Opcodes.cp_public_npc()
cp_use_scripted_npc_item = Opcodes.cp_use_scripted_npc_item()
case opcode do
# Chat handlers
^cp_general_chat ->
case Handler.Chat.handle_general_chat(packet, state) do
{:ok, new_state} -> new_state
_ -> state
end
^cp_party_chat ->
case Handler.Chat.handle_party_chat(packet, state) do
{:ok, new_state} -> new_state
_ -> state
end
^cp_whisper ->
case Handler.Chat.handle_whisper(packet, state) do
{:ok, new_state} -> new_state
_ -> state
end
# Player movement and actions
^cp_move_player ->
case Handler.Player.handle_move_player(packet, state) do
{:ok, new_state} -> new_state
_ -> state
end
^cp_change_map ->
case Handler.Player.handle_change_map(packet, state) do
{:ok, new_state} -> new_state
_ -> state
end
^cp_change_keymap ->
case Handler.Player.handle_change_keymap(packet, state) do
{:ok, new_state} -> new_state
_ -> state
end
^cp_skill_macro ->
case Handler.Player.handle_change_skill_macro(packet, state) do
{:ok, new_state} -> new_state
_ -> state
end
# Combat handlers (stubs for now)
^cp_close_range_attack ->
case Handler.Player.handle_close_range_attack(packet, state) do
{:ok, new_state} -> new_state
_ -> state
end
^cp_ranged_attack ->
case Handler.Player.handle_ranged_attack(packet, state) do
{:ok, new_state} -> new_state
_ -> state
end
^cp_magic_attack ->
case Handler.Player.handle_magic_attack(packet, state) do
{:ok, new_state} -> new_state
_ -> state
end
^cp_take_damage ->
case Handler.Player.handle_take_damage(packet, state) do
{:ok, new_state} -> new_state
_ -> state
end
# Inventory handlers
^cp_item_move ->
case Handler.Inventory.handle_item_move(packet, state) do
{:ok, new_state} -> new_state
_ -> state
end
^cp_item_sort ->
case Handler.Inventory.handle_item_sort(packet, state) do
{:ok, new_state} -> new_state
_ -> state
end
^cp_item_gather ->
case Handler.Inventory.handle_item_gather(packet, state) do
{:ok, new_state} -> new_state
_ -> state
end
^cp_use_item ->
case Handler.Inventory.handle_use_item(packet, state) do
{:ok, new_state} -> new_state
_ -> state
end
^cp_use_return_scroll ->
case Handler.Inventory.handle_use_return_scroll(packet, state) do
{:ok, new_state} -> new_state
_ -> state
end
^cp_use_scroll ->
case Handler.Inventory.handle_use_scroll(packet, state) do
{:ok, new_state} -> new_state
_ -> state
end
^cp_use_cash_item ->
case Handler.Inventory.handle_use_cash_item(packet, state) do
{:ok, new_state} -> new_state
_ -> state
end
# NPC handlers
^cp_npc_move ->
Handler.NPC.handle_npc_move(packet, self())
state
^cp_npc_talk ->
Handler.NPC.handle_npc_talk(packet, self())
state
^cp_npc_talk_more ->
Handler.NPC.handle_npc_more_talk(packet, self())
state
^cp_npc_shop ->
Handler.NPC.handle_npc_shop(packet, self())
state
^cp_storage ->
Handler.NPC.handle_storage(packet, self())
state
^cp_quest_action ->
Handler.NPC.handle_quest_action(packet, self())
state
^cp_repair ->
Handler.NPC.handle_repair(packet, self())
state
^cp_repair_all ->
Handler.NPC.handle_repair_all(self())
state
^cp_update_quest ->
Handler.NPC.handle_update_quest(packet, self())
state
^cp_use_item_quest ->
Handler.NPC.handle_use_item_quest(packet, self())
state
^cp_public_npc ->
Handler.NPC.handle_public_npc(packet, self())
state
^cp_use_scripted_npc_item ->
Handler.NPC.handle_use_scripted_npc_item(packet, self())
state
_ ->
Logger.debug("Unhandled channel opcode: 0x#{Integer.to_string(opcode, 16)}")
state
end
end
defp format_ip({a, b, c, d}) do
"#{a}.#{b}.#{c}.#{d}"
end
defp format_ip({a, b, c, d, e, f, g, h}) do
"#{a}:#{b}:#{c}:#{d}:#{e}:#{f}:#{g}:#{h}"
end
end