defmodule Odinsea.Channel.Handler.Alliance do @moduledoc """ Handles Guild Alliance operations. Ported from: src/handling/channel/handler/AllianceHandler.java Guild alliances allow multiple guilds to: - Share a common chat channel - Display alliance information - Coordinate activities ## Operations - 1: Load alliance info - 2: Leave alliance - 3: Invite guild to alliance - 4: Accept alliance invitation - 6: Expel guild from alliance - 7: Change alliance leader - 8: Update alliance titles (ranks) - 9: Change member guild rank - 10: Update alliance notice - 22: Deny alliance invitation ## Main Handlers - handle_alliance/2 - All alliance operations - handle_deny_invite/2 - Deny alliance invitation """ require Logger alias Odinsea.Net.Packet.In alias Odinsea.Game.Character alias Odinsea.World.Guild alias Odinsea.Channel.Packets # ============================================================================ # Alliance Operations # ============================================================================ @doc """ Handles all alliance operations (CP_ALLIANCE_OPERATION / 0xBA). Reference: AllianceHandler.HandleAlliance() """ def handle_alliance(packet, client_pid, denied \\ false) do case Character.get_state_by_client(client_pid) do {:ok, character_id, char_state} -> guild_id = char_state.guild_id # Check if in guild if guild_id <= 0 do send(client_pid, {:send_packet, Packets.enable_actions()}) :ok else # Get guild info # guild = World.Guild.get_guild(guild_id) op = In.decode_byte(packet) # Handle deny separately if op == 22 do handle_deny_invite(client_pid, character_id, char_state, guild_id) else handle_alliance_op(op, packet, client_pid, character_id, char_state, guild_id) end end {:error, reason} -> Logger.warn("Failed to handle alliance: #{inspect(reason)}") end end # ============================================================================ # Individual Operations # ============================================================================ # Operation 1: Load alliance info defp handle_alliance_op(1, _packet, client_pid, character_id, char_state, guild_id) do alliance_id = char_state.alliance_id if alliance_id > 0 do # TODO: Get alliance info from World.Alliance # packets = World.Alliance.get_alliance_info(alliance_id, false) # Enum.each(packets, fn packet -> send(client_pid, {:send_packet, packet}) end) Logger.debug("Alliance load: alliance #{alliance_id}, character #{character_id}") end :ok end # Operation 2: Leave alliance / Operation 6: Expel guild defp handle_alliance_op(op, packet, client_pid, character_id, char_state, guild_id) when op in [2, 6] do alliance_id = char_state.alliance_id guild_rank = char_state.guild_rank # Get target guild ID target_guild_id = if op == 6 and byte_size(packet.data) >= 4 do In.decode_int(packet) else guild_id end # Permission check: alliance rank <= 2, or own guild if guild_rank <= 2 or target_guild_id == guild_id do # TODO: Remove guild from alliance # World.Alliance.remove_guild_from_alliance(alliance_id, target_guild_id, target_guild_id != guild_id) Logger.debug("Alliance remove: guild #{target_guild_id} from alliance #{alliance_id}, character #{character_id}") end :ok end # Operation 3: Invite guild to alliance defp handle_alliance_op(3, packet, client_pid, character_id, char_state, guild_id) do alliance_id = char_state.alliance_id alliance_rank = char_state.alliance_rank # Get guild leader name to invite target_leader_name = In.decode_string(packet) # Only alliance leader (rank 1) can invite if alliance_rank == 1 do # TODO: Get target guild leader ID # target_leader_id = World.Guild.get_guild_leader(target_leader_name) # TODO: Get target character # target_char = ChannelServer.get_player_storage().get_character_by_id(target_leader_id) # TODO: Check if can invite # if World.Alliance.can_invite(alliance_id) do # # Send invite # target_char.client.send_packet(Packets.alliance_invite(alliance_name, character_id)) # World.Guild.set_invited_id(target_char.guild_id, alliance_id) # end Logger.debug("Alliance invite: to leader #{target_leader_name}, alliance #{alliance_id}, character #{character_id}") end :ok end # Operation 4: Accept alliance invitation defp handle_alliance_op(4, _packet, client_pid, character_id, char_state, guild_id) do # Get invited alliance ID # invited_alliance_id = World.Guild.get_invited_id(guild_id) # if invited_alliance_id > 0 do # # Add guild to alliance # success = World.Alliance.add_guild_to_alliance(invited_alliance_id, guild_id) # if not success do # # Send error message # end # # # Clear invited ID # World.Guild.set_invited_id(guild_id, 0) # end Logger.debug("Alliance accept: guild #{guild_id}, character #{character_id}") :ok end # Operation 7: Change alliance leader defp handle_alliance_op(7, packet, client_pid, character_id, char_state, guild_id) do alliance_id = char_state.alliance_id alliance_rank = char_state.alliance_rank # Only alliance leader can change leader if alliance_rank == 1 do new_leader_id = In.decode_int(packet) # TODO: Change alliance leader # World.Alliance.change_alliance_leader(alliance_id, new_leader_id) Logger.debug("Alliance leader change: to #{new_leader_id}, alliance #{alliance_id}, character #{character_id}") end :ok end # Operation 8: Update alliance titles/ranks defp handle_alliance_op(8, packet, client_pid, character_id, char_state, guild_id) do alliance_id = char_state.alliance_id alliance_rank = char_state.alliance_rank # Only alliance leader can update titles if alliance_rank == 1 do # Read 5 rank titles ranks = Enum.map(1..5, fn _ -> In.decode_string(packet) end) # TODO: Update alliance ranks # World.Alliance.update_alliance_ranks(alliance_id, ranks) Logger.debug("Alliance ranks update: alliance #{alliance_id}, character #{character_id}") end :ok end # Operation 9: Change member guild rank defp handle_alliance_op(9, packet, client_pid, character_id, char_state, guild_id) do alliance_id = char_state.alliance_id alliance_rank = char_state.alliance_rank # Alliance rank <= 2 can change ranks if alliance_rank <= 2 do target_guild_id = In.decode_int(packet) new_rank = In.decode_byte(packet) # TODO: Change guild rank in alliance # World.Alliance.change_alliance_rank(alliance_id, target_guild_id, new_rank) Logger.debug("Alliance rank change: guild #{target_guild_id} to rank #{new_rank}, alliance #{alliance_id}, character #{character_id}") end :ok end # Operation 10: Update alliance notice defp handle_alliance_op(10, packet, client_pid, character_id, char_state, guild_id) do alliance_id = char_state.alliance_id alliance_rank = char_state.alliance_rank # Alliance rank <= 2 can update notice if alliance_rank <= 2 do notice = In.decode_string(packet) # Check notice length (max 100) if String.length(notice) <= 100 do # TODO: Update alliance notice # World.Alliance.update_alliance_notice(alliance_id, notice) Logger.debug("Alliance notice update: alliance #{alliance_id}, character #{character_id}") end end :ok end # Unknown operation defp handle_alliance_op(op, _packet, _client_pid, character_id, _char_state, guild_id) do Logger.warning("Unknown alliance operation #{op} from character #{character_id}, guild #{guild_id}") :ok end # ============================================================================ # Deny Invite Handler # ============================================================================ @doc """ Handles deny alliance invitation (CP_DENY_ALLIANCE_REQUEST / 0xBB). Also called when op == 22 in alliance operation. Reference: AllianceHandler.DenyInvite() """ def handle_deny_invite(client_pid, character_id, char_state, guild_id) do # Get invited alliance ID # invited_alliance_id = World.Guild.get_invited_id(guild_id) # if invited_alliance_id > 0 do # # Get alliance leader # leader_id = World.Alliance.get_alliance_leader(invited_alliance_id) # # if leader_id > 0 do # # Notify leader of rejection # leader = ChannelServer.get_player_storage().get_character_by_id(leader_id) # if leader do # leader.drop_message(5, "#{guild.name} Guild has rejected the Guild Union invitation.") # end # end # # # Clear invited ID # World.Guild.set_invited_id(guild_id, 0) # end Logger.debug("Alliance invite denied: guild #{guild_id}, character #{character_id}") :ok end end