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_item_pickup = Opcodes.cp_item_pickup() 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() # Mob opcodes cp_move_life = Opcodes.cp_move_life() cp_auto_aggro = Opcodes.cp_auto_aggro() cp_mob_skill_delay_end = Opcodes.cp_mob_skill_delay_end() cp_mob_bomb = Opcodes.cp_mob_bomb() # Summon opcodes cp_move_summon = Opcodes.cp_move_summon() cp_summon_attack = Opcodes.cp_summon_attack() cp_damage_summon = Opcodes.cp_damage_summon() cp_sub_summon = Opcodes.cp_sub_summon() cp_remove_summon = Opcodes.cp_remove_summon() cp_move_dragon = Opcodes.cp_move_dragon() # Player operations cp_note_action = Opcodes.cp_note_action() cp_give_fame = Opcodes.cp_give_fame() cp_use_door = Opcodes.cp_use_door() cp_use_mech_door = Opcodes.cp_use_mech_door() cp_transform_player = Opcodes.cp_transform_player() cp_damage_reactor = Opcodes.cp_damage_reactor() cp_touch_reactor = Opcodes.cp_touch_reactor() cp_coconut = Opcodes.cp_coconut() cp_follow_request = Opcodes.cp_follow_request() cp_follow_reply = Opcodes.cp_follow_reply() cp_ring_action = Opcodes.cp_ring_action() cp_solomon = Opcodes.cp_solomon() cp_gach_exp = Opcodes.cp_gach_exp() cp_report = Opcodes.cp_report() cp_enter_pvp = Opcodes.cp_enter_pvp() cp_leave_pvp = Opcodes.cp_leave_pvp() cp_pvp_respawn = Opcodes.cp_pvp_respawn() cp_pvp_attack = Opcodes.cp_pvp_attack() # UI opcodes cp_cygnus_summon = Opcodes.cp_cygnus_summon() cp_game_poll = Opcodes.cp_game_poll() cp_ship_object = Opcodes.cp_ship_object() # BBS cp_bbs_operation = Opcodes.cp_bbs_operation() # Duey cp_duey_action = Opcodes.cp_duey_action() # Monster Carnival cp_monster_carnival = Opcodes.cp_monster_carnival() # Alliance cp_alliance_operation = Opcodes.cp_alliance_operation() cp_deny_alliance_request = Opcodes.cp_deny_alliance_request() # Item Maker / Crafting cp_item_maker = Opcodes.cp_item_maker() cp_use_recipe = Opcodes.cp_use_recipe() cp_make_extractor = Opcodes.cp_make_extractor() cp_use_bag = Opcodes.cp_use_bag() cp_start_harvest = Opcodes.cp_start_harvest() cp_stop_harvest = Opcodes.cp_stop_harvest() cp_profession_info = Opcodes.cp_profession_info() cp_craft_effect = Opcodes.cp_craft_effect() cp_craft_make = Opcodes.cp_craft_make() cp_craft_done = Opcodes.cp_craft_done() cp_use_pot = Opcodes.cp_use_pot() cp_clear_pot = Opcodes.cp_clear_pot() cp_feed_pot = Opcodes.cp_feed_pot() cp_cure_pot = Opcodes.cp_cure_pot() cp_reward_pot = Opcodes.cp_reward_pot() 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_item_pickup -> case Handler.Pickup.handle_item_pickup(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 # Mob handlers ^cp_move_life -> Handler.Mob.handle_mob_move(packet, self()) state ^cp_auto_aggro -> Handler.Mob.handle_auto_aggro(packet, self()) state ^cp_mob_skill_delay_end -> Handler.Mob.handle_mob_skill_delay_end(packet, self()) state ^cp_mob_bomb -> Handler.Mob.handle_mob_bomb(packet, self()) state # Summon handlers ^cp_move_summon -> Handler.Summon.handle_move_summon(packet, self()) state ^cp_summon_attack -> Handler.Summon.handle_summon_attack(packet, self()) state ^cp_damage_summon -> Handler.Summon.handle_damage_summon(packet, self()) state ^cp_sub_summon -> Handler.Summon.handle_sub_summon(packet, self()) state ^cp_remove_summon -> Handler.Summon.handle_remove_summon(packet, self()) state ^cp_move_dragon -> Handler.Summon.handle_move_dragon(packet, self()) state # Player handlers ^cp_note_action -> Handler.Players.handle_note(packet, self()) state ^cp_give_fame -> Handler.Players.handle_give_fame(packet, self()) state ^cp_use_door -> Handler.Players.handle_use_door(packet, self()) state ^cp_use_mech_door -> Handler.Players.handle_use_mech_door(packet, self()) state ^cp_transform_player -> Handler.Players.handle_transform_player(packet, self()) state ^cp_damage_reactor -> Handler.Players.handle_hit_reactor(packet, self()) state ^cp_touch_reactor -> Handler.Players.handle_touch_reactor(packet, self()) state ^cp_coconut -> Handler.Players.handle_hit_coconut(packet, self()) state ^cp_follow_request -> Handler.Players.handle_follow_request(packet, self()) state ^cp_follow_reply -> Handler.Players.handle_follow_reply(packet, self()) state ^cp_ring_action -> Handler.Players.handle_ring_action(packet, self()) state ^cp_solomon -> Handler.Players.handle_solomon(packet, self()) state ^cp_gach_exp -> Handler.Players.handle_gach_exp(packet, self()) state ^cp_report -> Handler.Players.handle_report(packet, self()) state ^cp_enter_pvp -> Handler.Players.handle_enter_pvp(packet, self()) state ^cp_leave_pvp -> Handler.Players.handle_leave_pvp(packet, self()) state ^cp_pvp_respawn -> Handler.Players.handle_respawn_pvp(packet, self()) state ^cp_pvp_attack -> Handler.Players.handle_attack_pvp(packet, self()) state # UI handlers ^cp_cygnus_summon -> Handler.UI.handle_cygnus_summon(packet, self()) state ^cp_game_poll -> Handler.UI.handle_game_poll(packet, self()) state ^cp_ship_object -> Handler.UI.handle_ship_object(packet, self()) state # BBS handler ^cp_bbs_operation -> Handler.BBS.handle_bbs_operation(packet, self()) state # Duey handler ^cp_duey_action -> Handler.Duey.handle_duey_operation(packet, self()) state # Monster Carnival handler ^cp_monster_carnival -> Handler.MonsterCarnival.handle_monster_carnival(packet, self()) state # Alliance handlers ^cp_alliance_operation -> Handler.Alliance.handle_alliance(packet, self()) state ^cp_deny_alliance_request -> Handler.Alliance.handle_deny_invite(packet, self()) state # Item Maker handlers ^cp_item_maker -> Handler.ItemMaker.handle_item_maker(packet, self()) state ^cp_use_recipe -> Handler.ItemMaker.handle_use_recipe(packet, self()) state ^cp_make_extractor -> Handler.ItemMaker.handle_make_extractor(packet, self()) state ^cp_use_bag -> Handler.ItemMaker.handle_use_bag(packet, self()) state ^cp_start_harvest -> Handler.ItemMaker.handle_start_harvest(packet, self()) state ^cp_stop_harvest -> Handler.ItemMaker.handle_stop_harvest(packet, self()) state ^cp_profession_info -> Handler.ItemMaker.handle_profession_info(packet, self()) state ^cp_craft_effect -> Handler.ItemMaker.handle_craft_effect(packet, self()) state ^cp_craft_make -> Handler.ItemMaker.handle_craft_make(packet, self()) state ^cp_craft_done -> Handler.ItemMaker.handle_craft_complete(packet, self()) state ^cp_use_pot -> Handler.ItemMaker.handle_use_pot(packet, self()) state ^cp_clear_pot -> Handler.ItemMaker.handle_clear_pot(packet, self()) state ^cp_feed_pot -> Handler.ItemMaker.handle_feed_pot(packet, self()) state ^cp_cure_pot -> Handler.ItemMaker.handle_cure_pot(packet, self()) state ^cp_reward_pot -> Handler.ItemMaker.handle_reward_pot(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