defmodule Odinsea.Channel.Handler.BBS do @moduledoc """ Handles Guild BBS (Bulletin Board System) operations. Ported from: src/handling/channel/handler/BBSHandler.java The Guild BBS allows guild members to: - Create and edit threads/posts - Reply to threads - Delete threads and replies (with permission checks) - List threads with pagination - View individual threads with replies ## Main Handlers - handle_bbs_operation/2 - All BBS operations (CRUD) """ require Logger alias Odinsea.Net.Packet.In alias Odinsea.Game.Character alias Odinsea.World.Guild alias Odinsea.Channel.Packets # ============================================================================ # BBS Operations # ============================================================================ @doc """ Handles all BBS operations (CP_BBS_OPERATION / 0xCD). Actions: - 0: Create new post / Edit existing post - 1: Delete a thread - 2: List threads (pagination) - 3: Display thread with replies - 4: Add reply to thread - 5: Delete reply from thread Reference: BBSHandler.BBSOperation() """ def handle_bbs_operation(packet, client_pid) do case Character.get_state_by_client(client_pid) do {:ok, character_id, char_state} -> guild_id = char_state.guild_id if guild_id <= 0 do Logger.debug("BBS operation rejected: character #{character_id} not in guild") :ok else action = In.decode_byte(packet) handle_bbs_action(action, packet, client_pid, character_id, char_state, guild_id) end {:error, reason} -> Logger.warn("Failed to handle BBS operation: #{inspect(reason)}") end end # ============================================================================ # Individual Actions # ============================================================================ # Action 0: Create or edit post defp handle_bbs_action(0, packet, client_pid, character_id, char_state, guild_id) do is_edit = In.decode_byte(packet) > 0 local_thread_id = if is_edit do In.decode_int(packet) else 0 end is_notice = In.decode_byte(packet) > 0 title = In.decode_string(packet) |> correct_length(25) text = In.decode_string(packet) |> correct_length(600) icon = In.decode_int(packet) # Validate icon valid_icon = validate_icon(icon, character_id) if valid_icon do if is_edit do # Edit existing thread edit_thread(guild_id, local_thread_id, title, text, icon, character_id, char_state.guild_rank) else # Create new thread create_thread(guild_id, title, text, icon, is_notice, character_id) end # Send updated thread list list_threads(client_pid, guild_id, 0) end Logger.debug("BBS create/edit: guild #{guild_id}, edit=#{is_edit}, notice=#{is_notice}, icon=#{icon}, character #{character_id}") :ok end # Action 1: Delete thread defp handle_bbs_action(1, packet, client_pid, character_id, char_state, guild_id) do local_thread_id = In.decode_int(packet) delete_thread(guild_id, local_thread_id, character_id, char_state.guild_rank) Logger.debug("BBS delete thread: guild #{guild_id}, thread #{local_thread_id}, character #{character_id}") :ok end # Action 2: List threads (pagination) defp handle_bbs_action(2, packet, client_pid, character_id, _char_state, guild_id) do start = In.decode_int(packet) list_threads(client_pid, guild_id, start * 10) Logger.debug("BBS list threads: guild #{guild_id}, start #{start}, character #{character_id}") :ok end # Action 3: Display thread defp handle_bbs_action(3, packet, client_pid, character_id, _char_state, guild_id) do local_thread_id = In.decode_int(packet) display_thread(client_pid, guild_id, local_thread_id) Logger.debug("BBS display thread: guild #{guild_id}, thread #{local_thread_id}, character #{character_id}") :ok end # Action 4: Add reply defp handle_bbs_action(4, packet, client_pid, character_id, _char_state, guild_id) do # Check rate limit (60 seconds between replies) # TODO: Implement rate limiting via CheatTracker local_thread_id = In.decode_int(packet) text = In.decode_string(packet) |> correct_length(25) add_reply(guild_id, local_thread_id, text, character_id) # Refresh thread display display_thread(client_pid, guild_id, local_thread_id) Logger.debug("BBS add reply: guild #{guild_id}, thread #{local_thread_id}, character #{character_id}") :ok end # Action 5: Delete reply defp handle_bbs_action(5, packet, client_pid, character_id, char_state, guild_id) do local_thread_id = In.decode_int(packet) reply_id = In.decode_int(packet) delete_reply(guild_id, local_thread_id, reply_id, character_id, char_state.guild_rank) # Refresh thread display display_thread(client_pid, guild_id, local_thread_id) Logger.debug("BBS delete reply: guild #{guild_id}, thread #{local_thread_id}, reply #{reply_id}, character #{character_id}") :ok end # Unknown action defp handle_bbs_action(action, _packet, _client_pid, character_id, _char_state, guild_id) do Logger.warning("Unknown BBS action #{action} from character #{character_id}, guild #{guild_id}") :ok end # ============================================================================ # BBS Backend Operations # ============================================================================ defp create_thread(guild_id, title, text, icon, is_notice, character_id) do # TODO: Call World.Guild.addBBSThread # Returns: local_thread_id Logger.debug("Create BBS thread: guild #{guild_id}, title '#{title}', character #{character_id}") :ok end defp edit_thread(guild_id, local_thread_id, title, text, icon, character_id, guild_rank) do # TODO: Call World.Guild.editBBSThread # Permission: thread owner OR guild rank <= 2 Logger.debug("Edit BBS thread: guild #{guild_id}, thread #{local_thread_id}, character #{character_id}") :ok end defp delete_thread(guild_id, local_thread_id, character_id, guild_rank) do # TODO: Call World.Guild.deleteBBSThread # Permission: thread owner OR guild rank <= 2 (masters/jr masters) Logger.debug("Delete BBS thread: guild #{guild_id}, thread #{local_thread_id}, character #{character_id}") :ok end defp list_threads(client_pid, guild_id, start) do # TODO: Get threads from World.Guild.getBBS # TODO: Build thread list packet # packet = Packets.bbs_thread_list(threads, start) # send(client_pid, {:send_packet, packet}) :ok end defp display_thread(client_pid, guild_id, local_thread_id) do # TODO: Get thread from World.Guild.getBBS # TODO: Find thread by local_thread_id # TODO: Build show thread packet # packet = Packets.show_thread(thread) # send(client_pid, {:send_packet, packet}) :ok end defp add_reply(guild_id, local_thread_id, text, character_id) do # TODO: Call World.Guild.addBBSReply Logger.debug("Add BBS reply: guild #{guild_id}, thread #{local_thread_id}, character #{character_id}") :ok end defp delete_reply(guild_id, local_thread_id, reply_id, character_id, guild_rank) do # TODO: Call World.Guild.deleteBBSReply # Permission: reply owner OR guild rank <= 2 Logger.debug("Delete BBS reply: guild #{guild_id}, thread #{local_thread_id}, reply #{reply_id}, character #{character_id}") :ok end # ============================================================================ # Helper Functions # ============================================================================ # Truncates string to max length if needed defp correct_length(string, max_size) when is_binary(string) do if String.length(string) > max_size do String.slice(string, 0, max_size) else string end end # Validates icon selection # Icons 0x64-0x6A (100-106) require NX items (5290000-5290006) defp validate_icon(icon, character_id) do cond do # NX icons - require specific items icon >= 0x64 and icon <= 0x6A -> # TODO: Check if player has item 5290000 + (icon - 0x64) # For now, allow all true # Standard icons (0-2) icon >= 0 and icon <= 2 -> true # Invalid icon true -> Logger.warning("Invalid BBS icon #{icon} from character #{character_id}") false end end end