defmodule Odinsea.Shop.Client do @moduledoc """ Client connection handler for the cash shop server. Handles: - Cash shop operations (buy, gift, wishlist, etc.) - MTS (Maple Trading System) operations - Coupon redemption - Inventory management """ use GenServer, restart: :temporary require Logger alias Odinsea.Net.Packet.In alias Odinsea.Net.Opcodes alias Odinsea.Shop.{Operation, MTS, Packets} alias Odinsea.Database.Context defstruct [ :socket, :ip, :state, :character_id, :account_id, :character, :account ] def start_link(socket) do GenServer.start_link(__MODULE__, socket) end @impl true def init(socket) do {:ok, {ip, _port}} = :inet.peername(socket) ip_string = format_ip(ip) Logger.info("Cash shop client connected from #{ip_string}") state = %__MODULE__{ socket: socket, ip: ip_string, state: :connected, character_id: nil, account_id: nil, character: nil, account: 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("Cash shop client disconnected: #{state.ip}") {:stop, :normal, state} {:error, reason} -> Logger.warning("Cash shop 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 # ============================================================================== # Packet Handling # ============================================================================== defp handle_packet(data, state) do packet = In.new(data) case In.decode_short(packet) do {opcode, packet} -> Logger.debug("Cash shop 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 cond do opcode == Opcodes.cp_player_loggedin() -> handle_migrate_in(packet, state) opcode == Opcodes.cp_cash_shop_update() -> # Cash shop operations handle_cash_shop_operation(packet, state) opcode == Opcodes.cp_mts_operation() -> # MTS operations handle_mts_operation(packet, state) opcode == Opcodes.cp_alive_ack() -> # Ping response - ignore state true -> Logger.debug("Unhandled cash shop opcode: 0x#{Integer.to_string(opcode, 16)}") state end end # ============================================================================== # Migrate In Handler # ============================================================================== defp handle_migrate_in(packet, state) do {char_id, packet} = In.decode_int(packet) {_client_ip, _packet} = In.decode_string(packet) # Skip client IP Logger.info("Cash shop migrate in for character #{char_id}") # Load character and account case Context.get_character(char_id) do nil -> Logger.error("Character #{char_id} not found") :gen_tcp.close(state.socket) state character -> case Context.get_account(character.account_id) do nil -> Logger.error("Account #{character.account_id} not found") :gen_tcp.close(state.socket) state account -> # Load gifts gifts = Context.load_gifts(character.id) character = %{character | cash_inventory: gifts ++ (character.cash_inventory || [])} # Send cash shop setup setup_packet = Packets.set_cash_shop(character) :gen_tcp.send(state.socket, setup_packet) # Send initial update Operation.cs_update(state.socket, character) %{state | state: :in_cash_shop, character_id: char_id, account_id: account.id, character: character, account: account } end end end # ============================================================================== # Cash Shop Operation Handler # ============================================================================== defp handle_cash_shop_operation(packet, state) do # Delegate to Operation module Operation.handle(packet, state) end # ============================================================================== # MTS Operation Handler # ============================================================================== defp handle_mts_operation(packet, state) do # Delegate to MTS module MTS.handle(packet, state) end # ============================================================================== # Utility Functions # ============================================================================== 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