defmodule Odinsea.Scripting.PlayerAPI do @moduledoc """ Player Interaction API for scripts. This module provides the interface that scripts use to interact with players, including dialogue functions, warping, item manipulation, and game mechanics. ## API Objects Different script types receive this API with different variable names: - `cm` - Conversation manager (NPC/Quest scripts) - `pi` - Portal interaction (Portal scripts) - `rm` - Reactor manager (Reactor scripts) - `qm` - Quest manager (Quest scripts - same as cm) ## Dialogue Functions ### Basic Dialogs - `send_ok/1` - Show OK button dialog - `send_next/1` - Show Next button dialog - `send_prev/1` - Show Previous button dialog - `send_next_prev/1` - Show Next and Previous buttons ### Choice Dialogs - `send_yes_no/1` - Yes/No choice - `send_accept_decline/1` - Accept/Decline choice - `send_simple/1` - Menu selection with #L tags ### Input Dialogs - `send_get_text/1` - Text input - `send_get_number/4` - Number input with min/max - `send_style/2` - Style selection (hair/face) - `ask_avatar/2` - Avatar preview ### Player-to-NPC Dialogs (Speaker) - `send_next_s/2` - Next with speaker type - `send_ok_s/2` - OK with speaker type - `send_yes_no_s/2` - Yes/No with speaker type ## Player Actions ### Warping - `warp/1` - Warp to map (random portal) - `warp/2` - Warp to map with specific portal - `warp_instanced/1` - Warp to event instance map ### Items - `gain_item/2` - Give/take items - `gain_item_period/3` - Give item with expiration - `have_item/1` - Check if player has item - `can_hold/1` - Check if player can hold item ### Stats - `gain_meso/1` - Give/take meso - `gain_exp/1` - Give EXP - `change_job/1` - Change job - `max_stats/0` - Max all stats (GM) ## Example Usage def start(cm) do PlayerAPI.send_next(cm, "Hello there!") end def action(cm, mode, type, selection) do if mode == 1 do PlayerAPI.send_yes_no(cm, "Would you like to go to Henesys?") else PlayerAPI.dispose(cm) end end """ require Logger alias Odinsea.Game.{Character, Inventory, Item, Map} alias Odinsea.Channel.Packets # Message type codes (matching Java implementation) @msg_ok 0 @msg_next 0 @msg_prev 0 @msg_next_prev 0 @msg_yes_no 2 @msg_get_text 3 @msg_get_number 4 @msg_simple 5 @msg_accept_decline 0x0E @msg_style 9 # ============================================================================ # Types # ============================================================================ @type t :: %__MODULE__{ client_pid: pid(), character_id: integer(), npc_id: integer(), quest_id: integer() | nil, manager_pid: pid() | nil, get_text: String.t() | nil } defstruct [ :client_pid, :character_id, :npc_id, :quest_id, :manager_pid, :get_text ] # ============================================================================ # Constructor # ============================================================================ @doc """ Creates a new Player API instance. ## Parameters - `client_pid` - Client process PID - `character_id` - Character ID - `npc_id` - NPC ID (or portal/reactor ID) - `quest_id` - Quest ID (nil for non-quest) - `manager_pid` - NPCManager PID (for disposal) """ @spec new(pid(), integer(), integer(), integer() | nil, pid() | nil) :: t() def new(client_pid, character_id, npc_id, quest_id \\ nil, manager_pid \\ nil) do %__MODULE__{ client_pid: client_pid, character_id: character_id, npc_id: npc_id, quest_id: quest_id, manager_pid: manager_pid, get_text: nil } end # ============================================================================ # Conversation Control # ============================================================================ @doc """ Ends the conversation. ## Parameters - `api` - The API struct """ @spec dispose(t()) :: :ok def dispose(%__MODULE__{manager_pid: nil}), do: :ok def dispose(%__MODULE__{manager_pid: pid, client_pid: cpid, character_id: cid}) do Odinsea.Scripting.NPCManager.dispose(cpid, cid) end @doc """ Schedules disposal on the next action. ## Parameters - `api` - The API struct """ @spec safe_dispose(t()) :: :ok def safe_dispose(%__MODULE__{manager_pid: nil}), do: :ok def safe_dispose(%__MODULE__{client_pid: cpid, character_id: cid}) do Odinsea.Scripting.NPCManager.safe_dispose(cpid, cid) end @doc """ Sets the last message type for validation. ## Parameters - `api` - The API struct - `msg_type` - Message type code """ @spec set_last_msg(t(), integer()) :: :ok def set_last_msg(%__MODULE__{client_pid: cpid, character_id: cid}, msg_type) do Odinsea.Scripting.NPCManager.set_last_msg(cpid, cid, msg_type) end # ============================================================================ # Basic Dialogs # ============================================================================ @doc """ Sends an OK dialog. ## Parameters - `api` - The API struct - `text` - Dialog text (can include #b#k tags) """ @spec send_ok(t(), String.t()) :: :ok def send_ok(api, text) do send_ok_npc(api, text, api.npc_id) end @doc """ Sends an OK dialog with specific NPC ID. ## Parameters - `api` - The API struct - `text` - Dialog text - `npc_id` - NPC ID to display """ @spec send_ok_npc(t(), String.t(), integer()) :: :ok def send_ok_npc(api, text, npc_id) do # TODO: Send NPCTalk packet # Packet: LP_NPCTalk (opcode varies) # Type: 0, text, "00 00" # Placeholder: log and set last message Logger.debug("NPC #{npc_id} says (OK): #{text}") set_last_msg(api, @msg_ok) :ok end @doc """ Sends a Next dialog (user clicks Next to continue). ## Parameters - `api` - The API struct - `text` - Dialog text """ @spec send_next(t(), String.t()) :: :ok def send_next(api, text) do send_next_npc(api, text, api.npc_id) end @doc """ Sends a Next dialog with specific NPC ID. """ @spec send_next_npc(t(), String.t(), integer()) :: :ok def send_next_npc(api, text, npc_id) do # Check for #L tags (would DC otherwise) if String.contains?(text, "#L") do send_simple_npc(api, text, npc_id) else # TODO: Send NPCTalk packet with "00 01" style Logger.debug("NPC #{npc_id} says (Next): #{text}") set_last_msg(api, @msg_next) end :ok end @doc """ Sends a Previous dialog. """ @spec send_prev(t(), String.t()) :: :ok def send_prev(api, text) do send_prev_npc(api, text, api.npc_id) end @doc """ Sends a Previous dialog with specific NPC ID. """ @spec send_prev_npc(t(), String.t(), integer()) :: :ok def send_prev_npc(api, text, npc_id) do if String.contains?(text, "#L") do send_simple_npc(api, text, npc_id) else # TODO: Send NPCTalk packet with "01 00" style Logger.debug("NPC #{npc_id} says (Prev): #{text}") set_last_msg(api, @msg_prev) end :ok end @doc """ Sends a Next/Previous dialog. """ @spec send_next_prev(t(), String.t()) :: :ok def send_next_prev(api, text) do send_next_prev_npc(api, text, api.npc_id) end @doc """ Sends a Next/Previous dialog with specific NPC ID. """ @spec send_next_prev_npc(t(), String.t(), integer()) :: :ok def send_next_prev_npc(api, text, npc_id) do if String.contains?(text, "#L") do send_simple_npc(api, text, npc_id) else # TODO: Send NPCTalk packet with "01 01" style Logger.debug("NPC #{npc_id} says (Next/Prev): #{text}") set_last_msg(api, @msg_next_prev) end :ok end # ============================================================================ # Speaker Dialogs (Player-to-NPC) # ============================================================================ @doc """ Sends a Next dialog with speaker. ## Parameters - `api` - The API struct - `text` - Dialog text - `speaker_type` - Speaker type (0=NPC, 1=No ESC, 3=Player) """ @spec send_next_s(t(), String.t(), integer()) :: :ok def send_next_s(api, text, speaker_type) do send_next_s_npc(api, text, speaker_type, api.npc_id) end @doc """ Sends a Next dialog with speaker and specific NPC ID. """ @spec send_next_s_npc(t(), String.t(), integer(), integer()) :: :ok def send_next_s_npc(api, text, speaker_type, npc_id) do # TODO: Send NPCTalk with speaker Logger.debug("NPC #{npc_id} says (NextS, type=#{speaker_type}): #{text}") set_last_msg(api, @msg_next) :ok end @doc """ Sends an OK dialog with speaker. """ @spec send_ok_s(t(), String.t(), integer()) :: :ok def send_ok_s(api, text, speaker_type) do # TODO: Send NPCTalk with speaker Logger.debug("NPC #{api.npc_id} says (OkS, type=#{speaker_type}): #{text}") set_last_msg(api, @msg_ok) :ok end @doc """ Sends a Yes/No dialog with speaker. """ @spec send_yes_no_s(t(), String.t(), integer()) :: :ok def send_yes_no_s(api, text, speaker_type) do # TODO: Send NPCTalk with speaker Logger.debug("NPC #{api.npc_id} asks (YesNoS, type=#{speaker_type}): #{text}") set_last_msg(api, @msg_yes_no) :ok end @doc """ Sends a Player-to-NPC dialog (convenience). """ @spec player_to_npc(t(), String.t()) :: :ok def player_to_npc(api, text) do send_next_s(api, text, 3) end # ============================================================================ # Choice Dialogs # ============================================================================ @doc """ Sends a Yes/No dialog. ## Parameters - `api` - The API struct - `text` - Question text """ @spec send_yes_no(t(), String.t()) :: :ok def send_yes_no(api, text) do send_yes_no_npc(api, text, api.npc_id) end @doc """ Sends a Yes/No dialog with specific NPC ID. """ @spec send_yes_no_npc(t(), String.t(), integer()) :: :ok def send_yes_no_npc(api, text, npc_id) do if String.contains?(text, "#L") do send_simple_npc(api, text, npc_id) else # TODO: Send NPCTalk type 2 Logger.debug("NPC #{npc_id} asks (Yes/No): #{text}") set_last_msg(api, @msg_yes_no) end :ok end @doc """ Sends an Accept/Decline dialog. """ @spec send_accept_decline(t(), String.t()) :: :ok def send_accept_decline(api, text) do send_accept_decline_npc(api, text, api.npc_id) end @doc """ Sends an Accept/Decline dialog with specific NPC ID. """ @spec send_accept_decline_npc(t(), String.t(), integer()) :: :ok def send_accept_decline_npc(api, text, npc_id) do if String.contains?(text, "#L") do send_simple_npc(api, text, npc_id) else # TODO: Send NPCTalk type 0x0E/0x0F Logger.debug("NPC #{npc_id} asks (Accept/Decline): #{text}") set_last_msg(api, @msg_accept_decline) end :ok end @doc """ Sends a simple menu dialog. Text should contain #L tags for menu items: `#L0#Option 1#l\r\n#L1#Option 2#l` """ @spec send_simple(t(), String.t()) :: :ok def send_simple(api, text) do send_simple_npc(api, text, api.npc_id) end @doc """ Sends a simple menu dialog with specific NPC ID. """ @spec send_simple_npc(t(), String.t(), integer()) :: :ok def send_simple_npc(api, text, npc_id) do if not String.contains?(text, "#L") do # Would DC otherwise send_next_npc(api, text, npc_id) else # TODO: Send NPCTalk type 5 Logger.debug("NPC #{npc_id} menu: #{text}") set_last_msg(api, @msg_simple) end :ok end @doc """ Sends a simple menu with selection array. ## Parameters - `api` - The API struct - `text` - Intro text - `selections` - List of menu options """ @spec send_simple_selections(t(), String.t(), [String.t()]) :: :ok def send_simple_selections(api, text, selections) do menu_text = if length(selections) > 0 do text <> "#b\r\n" <> build_menu(selections) else text end send_simple(api, menu_text) end defp build_menu(selections) do selections |> Enum.with_index() |> Enum.map_join("\r\n", fn {item, idx} -> "#L#{idx}##{item}#l" end) end # ============================================================================ # Input Dialogs # ============================================================================ @doc """ Sends a text input dialog. ## Parameters - `api` - The API struct - `text` - Prompt text """ @spec send_get_text(t(), String.t()) :: :ok def send_get_text(api, text) do send_get_text_npc(api, text, api.npc_id) end @doc """ Sends a text input dialog with specific NPC ID. """ @spec send_get_text_npc(t(), String.t(), integer()) :: :ok def send_get_text_npc(api, text, npc_id) do if String.contains?(text, "#L") do send_simple_npc(api, text, npc_id) else # TODO: Send NPCTalkText Logger.debug("NPC #{npc_id} asks for text: #{text}") set_last_msg(api, @msg_get_text) end :ok end @doc """ Sends a text input dialog with constraints. ## Parameters - `api` - The API struct - `text` - Prompt text - `min` - Minimum length - `max` - Maximum length - `default` - Default text """ @spec send_get_text_constrained(t(), String.t(), integer(), integer(), String.t()) :: :ok def send_get_text_constrained(api, text, min, max, default) do # TODO: Send NPCTalkText with constraints Logger.debug("NPC #{api.npc_id} asks for text (#{min}-#{max}): #{text}") set_last_msg(api, @msg_get_text) :ok end @doc """ Sends a number input dialog. ## Parameters - `api` - The API struct - `text` - Prompt text - `default` - Default value - `min` - Minimum value - `max` - Maximum value """ @spec send_get_number(t(), String.t(), integer(), integer(), integer()) :: :ok def send_get_number(api, text, default, min, max) do if String.contains?(text, "#L") do send_simple(api, text) else # TODO: Send NPCTalkNum Logger.debug("NPC #{api.npc_id} asks for number (#{min}-#{max}, default #{default}): #{text}") set_last_msg(api, @msg_get_number) end :ok end @doc """ Sends a style selection dialog (for hair/face). ## Parameters - `api` - The API struct - `text` - Prompt text - `styles` - List of style IDs """ @spec send_style(t(), String.t(), [integer()]) :: :ok def send_style(api, text, styles) do send_style_paged(api, text, styles, 0) end @doc """ Sends a style selection dialog with page. """ @spec send_style_paged(t(), String.t(), [integer()], integer()) :: :ok def send_style_paged(api, text, styles, page) do # TODO: Send NPCTalkStyle Logger.debug("NPC #{api.npc_id} style selection (page #{page}): #{length(styles)} options") set_last_msg(api, @msg_style) :ok end @doc """ Sends an avatar selection dialog. """ @spec ask_avatar(t(), String.t(), [integer()]) :: :ok def ask_avatar(api, text, styles) do send_style_paged(api, text, styles, 0) end @doc """ Sends a map selection dialog. """ @spec ask_map_selection(t(), String.t()) :: :ok def ask_map_selection(api, selection_string) do # TODO: Send MapSelection Logger.debug("NPC #{api.npc_id} map selection") set_last_msg(api, 0x10) :ok end # ============================================================================ # Get/Set Text # ============================================================================ @doc """ Sets the text received from player input. ## Parameters - `api` - The API struct - `text` - The text value """ @spec set_get_text(t(), String.t()) :: t() def set_get_text(%__MODULE__{} = api, text) do %{api | get_text: text} end @doc """ Gets the text received from player input. """ @spec get_get_text(t()) :: String.t() | nil def get_get_text(%__MODULE__{get_text: text}), do: text # ============================================================================ # Character Appearance # ============================================================================ @doc """ Sets the character's hair style. ## Parameters - `api` - The API struct - `hair` - Hair style ID """ @spec set_hair(t(), integer()) :: :ok def set_hair(api, hair) do # TODO: Update character hair and send packet Logger.debug("Set hair to #{hair}") :ok end @doc """ Sets the character's face. """ @spec set_face(t(), integer()) :: :ok def set_face(api, face) do Logger.debug("Set face to #{face}") :ok end @doc """ Sets the character's skin color. """ @spec set_skin(t(), integer()) :: :ok def set_skin(api, color) do Logger.debug("Set skin color to #{color}") :ok end @doc """ Sets a random avatar from given options. """ @spec set_random_avatar(t(), integer(), [integer()]) :: integer() def set_random_avatar(api, ticket, options) do if have_item(api, ticket) do gain_item(api, ticket, -1) style = Enum.random(options) cond do style < 100 -> set_skin(api, style) style < 30000 -> set_face(api, style) true -> set_hair(api, style) end 1 else -1 end end @doc """ Sets a specific avatar style. """ @spec set_avatar(t(), integer(), integer()) :: integer() def set_avatar(api, ticket, style) do if have_item(api, ticket) do gain_item(api, ticket, -1) cond do style < 100 -> set_skin(api, style) style < 30000 -> set_face(api, style) true -> set_hair(api, style) end 1 else -1 end end # ============================================================================ # Warping # ============================================================================ @doc """ Warps the player to a map. ## Parameters - `api` - The API struct - `map_id` - Target map ID """ @spec warp(t(), integer()) :: :ok def warp(api, map_id) do # Use random portal warp_portal(api, map_id, 0) end @doc """ Warps the player to a map with specific portal. ## Parameters - `api` - The API struct - `map_id` - Target map ID - `portal` - Portal ID or name """ @spec warp_portal(t(), integer(), integer() | String.t()) :: :ok def warp_portal(api, map_id, portal) do Logger.debug("Warping to map #{map_id}, portal #{inspect(portal)}") # TODO: Send warp packet and handle map change :ok end @doc """ Warps the player to an instanced map. """ @spec warp_instanced(t(), integer()) :: :ok def warp_instanced(api, map_id) do Logger.debug("Warping to instanced map #{map_id}") :ok end @doc """ Warps all players on the current map. """ @spec warp_map(t(), integer(), integer()) :: :ok def warp_map(api, map_id, portal) do Logger.debug("Warping all players to map #{map_id}") :ok end @doc """ Warps the entire party. """ @spec warp_party(t(), integer()) :: :ok def warp_party(api, map_id) do Logger.debug("Warping party to map #{map_id}") :ok end @doc """ Plays the portal sound effect. """ @spec play_portal_se(t()) :: :ok def play_portal_se(_api) do # TODO: Send portal SE packet :ok end # ============================================================================ # Items # ============================================================================ @doc """ Gives or takes items from the player. ## Parameters - `api` - The API struct - `item_id` - Item ID - `quantity` - Positive to give, negative to take """ @spec gain_item(t(), integer(), integer()) :: :ok def gain_item(api, item_id, quantity) do gain_item_full(api, item_id, quantity, false, 0, -1, "") end @doc """ Gives items with random stats. """ @spec gain_item_random(t(), integer(), integer(), boolean()) :: :ok def gain_item_random(api, item_id, quantity, random_stats) do gain_item_full(api, item_id, quantity, random_stats, 0, -1, "") end @doc """ Gives items with slot bonus. """ @spec gain_item_slots(t(), integer(), integer(), boolean(), integer()) :: :ok def gain_item_slots(api, item_id, quantity, random_stats, slots) do gain_item_full(api, item_id, quantity, random_stats, 0, slots, "") end @doc """ Gives items with expiration period. """ @spec gain_item_period(t(), integer(), integer(), integer()) :: :ok def gain_item_period(api, item_id, quantity, days) do gain_item_full(api, item_id, quantity, false, days, -1, "") end @doc """ Gives items with full options. """ @spec gain_item_full(t(), integer(), integer(), boolean(), integer(), integer(), String.t()) :: :ok def gain_item_full(api, item_id, quantity, random_stats, period, slots, owner) do Logger.debug("Gain item #{item_id} x#{quantity} (random=#{random_stats}, period=#{period})") # TODO: Add item to inventory :ok end @doc """ Checks if player has an item. """ @spec have_item(t(), integer()) :: boolean() def have_item(api, item_id) do have_item_count(api, item_id, 1) end @doc """ Checks if player has at least quantity of an item. """ @spec have_item_count(t(), integer(), integer()) :: boolean() def have_item_count(_api, _item_id, _quantity) do # TODO: Check inventory true end @doc """ Removes an item from inventory. """ @spec remove_item(t(), integer()) :: boolean() def remove_item(api, item_id) do # TODO: Remove item from inventory Logger.debug("Remove item #{item_id}") true end @doc """ Checks if player can hold an item. """ @spec can_hold(t(), integer()) :: boolean() def can_hold(_api, _item_id) do # TODO: Check inventory space true end @doc """ Checks if player can hold quantity of an item. """ @spec can_hold_quantity(t(), integer(), integer()) :: boolean() def can_hold_quantity(_api, _item_id, _quantity) do # TODO: Check inventory space true end # ============================================================================ # Meso/EXP # ============================================================================ @doc """ Gives or takes meso. ## Parameters - `api` - The API struct - `amount` - Positive to give, negative to take """ @spec gain_meso(t(), integer()) :: :ok def gain_meso(_api, amount) do Logger.debug("Gain meso: #{amount}") # TODO: Update meso :ok end @doc """ Gets the player's current meso. """ @spec get_meso(t()) :: integer() def get_meso(_api) do # TODO: Get meso from character 0 end @doc """ Gives EXP to the player. """ @spec gain_exp(t(), integer()) :: :ok def gain_exp(_api, amount) do Logger.debug("Gain EXP: #{amount}") # TODO: Add EXP :ok end @doc """ Gives EXP with rate multiplier. """ @spec gain_exp_r(t(), integer()) :: :ok def gain_exp_r(api, amount) do # TODO: Apply rate multiplier gain_exp(api, amount) end # ============================================================================ # Jobs and Skills # ============================================================================ @doc """ Changes the player's job. """ @spec change_job(t(), integer()) :: :ok def change_job(_api, job_id) do Logger.debug("Change job to #{job_id}") # TODO: Change job and send packet :ok end @doc """ Gets the player's current job. """ @spec get_job(t()) :: integer() def get_job(_api) do # TODO: Get job from character 0 end @doc """ Teaches a skill to the player. """ @spec teach_skill(t(), integer(), integer(), integer()) :: :ok def teach_skill(_api, skill_id, level, master_level) do Logger.debug("Teach skill #{skill_id} level #{level}/#{master_level}") # TODO: Add skill :ok end @doc """ Teaches a skill with max master level. """ @spec teach_skill_max(t(), integer(), integer()) :: :ok def teach_skill_max(api, skill_id, level) do # TODO: Get max level from skill data teach_skill(api, skill_id, level, 20) end @doc """ Checks if player has a skill. """ @spec has_skill(t(), integer()) :: boolean() def has_skill(_api, _skill_id) do # TODO: Check skills false end @doc """ Maxes all skills for the player (GM). """ @spec max_all_skills(t()) :: :ok def max_all_skills(_api) do Logger.debug("Max all skills") # TODO: Max all skills :ok end # ============================================================================ # Stats # ============================================================================ @doc """ Maxes all stats for the player (GM). """ @spec max_stats(t()) :: :ok def max_stats(_api) do Logger.debug("Max stats") # TODO: Set all stats to max :ok end @doc """ Adds AP (ability points). """ @spec gain_ap(t(), integer()) :: :ok def gain_ap(_api, amount) do Logger.debug("Gain AP: #{amount}") :ok end @doc """ Resets stats to specified values. """ @spec reset_stats(t(), integer(), integer(), integer(), integer()) :: :ok def reset_stats(_api, str, dex, int, luk) do Logger.debug("Reset stats to STR=#{str} DEX=#{dex} INT=#{int} LUK=#{luk}") :ok end @doc """ Gets a player stat by name. Valid stat names: "LVL", "STR", "DEX", "INT", "LUK", "HP", "MP", "MAXHP", "MAXMP", "RAP", "RSP", "GID", "GRANK", "ARANK", "GM", "ADMIN", "GENDER", "FACE", "HAIR" """ @spec get_player_stat(t(), String.t()) :: integer() def get_player_stat(_api, stat_name) do Logger.debug("Get stat: #{stat_name}") # TODO: Return stat value case stat_name do "LVL" -> 1 "GM" -> 0 "ADMIN" -> 0 _ -> 0 end end # ============================================================================ # Quests # ============================================================================ @doc """ Starts a quest. """ @spec start_quest(t(), integer()) :: :ok def start_quest(_api, quest_id) do Logger.debug("Start quest #{quest_id}") # TODO: Start quest :ok end @doc """ Completes a quest. """ @spec complete_quest(t(), integer()) :: :ok def complete_quest(_api, quest_id) do Logger.debug("Complete quest #{quest_id}") # TODO: Complete quest :ok end @doc """ Forfeits a quest. """ @spec forfeit_quest(t(), integer()) :: :ok def forfeit_quest(_api, quest_id) do Logger.debug("Forfeit quest #{quest_id}") # TODO: Forfeit quest :ok end @doc """ Forces a quest start. """ @spec force_start_quest(t(), integer()) :: :ok def force_start_quest(_api, quest_id) do Logger.debug("Force start quest #{quest_id}") :ok end @doc """ Forces a quest complete. """ @spec force_complete_quest(t(), integer()) :: :ok def force_complete_quest(_api, quest_id) do Logger.debug("Force complete quest #{quest_id}") :ok end @doc """ Gets quest custom data. """ @spec get_quest_custom_data(t()) :: String.t() | nil def get_quest_custom_data(_api) do nil end @doc """ Sets quest custom data. """ @spec set_quest_custom_data(t(), String.t()) :: :ok def set_quest_custom_data(_api, data) do Logger.debug("Set quest custom data: #{data}") :ok end @doc """ Gets quest record. """ @spec get_quest_record(t(), integer()) :: term() def get_quest_record(_api, _quest_id) do nil end @doc """ Gets quest status (0 = not started, 1 = in progress, 2 = completed). """ @spec get_quest_status(t(), integer()) :: integer() def get_quest_status(_api, _quest_id) do 0 end @doc """ Checks if quest is active. """ @spec is_quest_active(t(), integer()) :: boolean() def is_quest_active(api, quest_id) do get_quest_status(api, quest_id) == 1 end @doc """ Checks if quest is finished. """ @spec is_quest_finished(t(), integer()) :: boolean() def is_quest_finished(api, quest_id) do get_quest_status(api, quest_id) == 2 end # ============================================================================ # Map/Mob Operations # ============================================================================ @doc """ Gets the current map ID. """ @spec get_map_id(t()) :: integer() def get_map_id(_api) do # TODO: Get current map 100000000 end @doc """ Gets map reference. """ @spec get_map(t(), integer()) :: term() def get_map(_api, map_id) do # TODO: Get map instance %{id: map_id} end @doc """ Spawns a monster. """ @spec spawn_monster(t(), integer()) :: :ok def spawn_monster(api, mob_id) do spawn_monster_qty(api, mob_id, 1) end @doc """ Spawns multiple monsters. """ @spec spawn_monster_qty(t(), integer(), integer()) :: :ok def spawn_monster_qty(_api, mob_id, qty) do Logger.debug("Spawn monster #{mob_id} x#{qty}") # TODO: Spawn monsters :ok end @doc """ Spawns monster at position. """ @spec spawn_monster_pos(t(), integer(), integer(), integer(), integer()) :: :ok def spawn_monster_pos(_api, mob_id, qty, x, y) do Logger.debug("Spawn monster #{mob_id} x#{qty} at (#{x}, #{y})") :ok end @doc """ Kills all monsters on current map. """ @spec kill_all_mob(t()) :: :ok def kill_all_mob(_api) do Logger.debug("Kill all monsters") :ok end @doc """ Kills specific monster. """ @spec kill_mob(t(), integer()) :: :ok def kill_mob(_api, mob_id) do Logger.debug("Kill monster #{mob_id}") :ok end @doc """ Spawns an NPC. """ @spec spawn_npc(t(), integer()) :: :ok def spawn_npc(api, npc_id) do # TODO: Get player position and spawn Logger.debug("Spawn NPC #{npc_id}") :ok end @doc """ Spawns NPC at position. """ @spec spawn_npc_pos(t(), integer(), integer(), integer()) :: :ok def spawn_npc_pos(_api, npc_id, x, y) do Logger.debug("Spawn NPC #{npc_id} at (#{x}, #{y})") :ok end @doc """ Removes an NPC from current map. """ @spec remove_npc(t(), integer()) :: :ok def remove_npc(_api, npc_id) do Logger.debug("Remove NPC #{npc_id}") :ok end @doc """ Resets a map. """ @spec reset_map(t(), integer()) :: :ok def reset_map(_api, map_id) do Logger.debug("Reset map #{map_id}") # TODO: Reset map :ok end # ============================================================================ # Party Operations # ============================================================================ @doc """ Checks if player is party leader. """ @spec is_leader(t()) :: boolean() def is_leader(_api) do # TODO: Check party leadership false end @doc """ Gets party members in current map. """ @spec party_members_in_map(t()) :: integer() def party_members_in_map(_api) do # TODO: Count party members in map 1 end @doc """ Gets all party members. """ @spec get_party_members(t()) :: [term()] def get_party_members(_api) do [] end @doc """ Checks if all party members are in current map. """ @spec all_members_here(t()) :: boolean() def all_members_here(_api) do true end @doc """ Warps party with EXP reward. """ @spec warp_party_with_exp(t(), integer(), integer()) :: :ok def warp_party_with_exp(api, map_id, exp) do warp_party(api, map_id) gain_exp(api, exp) :ok end @doc """ Warps party with EXP and meso reward. """ @spec warp_party_with_exp_meso(t(), integer(), integer(), integer()) :: :ok def warp_party_with_exp_meso(api, map_id, exp, meso) do warp_party(api, map_id) gain_exp(api, exp) gain_meso(api, meso) :ok end # ============================================================================ # Guild Operations # ============================================================================ @doc """ Gets guild ID. """ @spec get_guild_id(t()) :: integer() def get_guild_id(_api) do 0 end @doc """ Increases guild capacity. """ @spec increase_guild_capacity(t(), boolean()) :: boolean() def increase_guild_capacity(_api, _true_max) do false end @doc """ Displays guild ranks. """ @spec display_guild_ranks(t()) :: :ok def display_guild_ranks(_api) do :ok end # ============================================================================ # Messages # ============================================================================ @doc """ Sends a message to the player. """ @spec player_message(t(), String.t()) :: :ok def player_message(api, message) do player_message_type(api, 5, message) end @doc """ Sends a message with type. Types: 1 = Popup, 5 = Chat, -1 = Important """ @spec player_message_type(t(), integer(), String.t()) :: :ok def player_message_type(_api, type, message) do Logger.debug("Player message (#{type}): #{message}") # TODO: Send message packet :ok end @doc """ Sends a message to the map. """ @spec map_message(t(), String.t()) :: :ok def map_message(api, message) do map_message_type(api, 5, message) end @doc """ Sends a message to the map with type. """ @spec map_message_type(t(), integer(), String.t()) :: :ok def map_message_type(_api, type, message) do Logger.debug("Map message (#{type}): #{message}") # TODO: Broadcast message :ok end @doc """ Sends a world message. """ @spec world_message(t(), integer(), String.t()) :: :ok def world_message(_api, type, message) do Logger.debug("World message (#{type}): #{message}") # TODO: Broadcast to world :ok end @doc """ Sends a guild message. """ @spec guild_message(t(), String.t()) :: :ok def guild_message(_api, message) do Logger.debug("Guild message: #{message}") :ok end @doc """ Shows quest message. """ @spec show_quest_msg(t(), String.t()) :: :ok def show_quest_msg(_api, msg) do Logger.debug("Quest message: #{msg}") :ok end # ============================================================================ # Storage/Shop # ============================================================================ @doc """ Opens storage. """ @spec open_storage(t()) :: :ok def open_storage(_api) do Logger.debug("Open storage") :ok end @doc """ Opens a shop. """ @spec open_shop(t(), integer()) :: :ok def open_shop(_api, shop_id) do Logger.debug("Open shop #{shop_id}") :ok end # ============================================================================ # Event/Instance # ============================================================================ @doc """ Gets event manager. """ @spec get_event_manager(t(), String.t()) :: term() def get_event_manager(_api, _event_name) do nil end @doc """ Gets event instance. """ @spec get_event_instance(t()) :: term() def get_event_instance(_api) do nil end @doc """ Removes player from instance. """ @spec remove_player_from_instance(t()) :: boolean() def remove_player_from_instance(_api) do false end @doc """ Checks if player is in an instance. """ @spec is_player_instance(t()) :: boolean() def is_player_instance(_api) do false end # ============================================================================ # Miscellaneous # ============================================================================ @doc """ Opens an NPC by ID. """ @spec open_npc(t(), integer()) :: :ok def open_npc(%__MODULE__{client_pid: cpid, character_id: cid}, npc_id) do Odinsea.Scripting.NPCManager.start_conversation(cpid, cid, npc_id) end @doc """ Opens an NPC with specific script. """ @spec open_npc_script(t(), integer(), String.t()) :: :ok def open_npc_script(%__MODULE__{client_pid: cpid, character_id: cid}, npc_id, script_name) do Odinsea.Scripting.NPCManager.start_conversation(cpid, cid, npc_id, script_name: script_name) end @doc """ Gets player name. """ @spec get_name(t()) :: String.t() def get_name(_api) do "Unknown" end @doc """ Gets channel number. """ @spec get_channel(t()) :: integer() def get_channel(_api) do 1 end @doc """ Adds HP. """ @spec add_hp(t(), integer()) :: :ok def add_hp(_api, amount) do Logger.debug("Add HP: #{amount}") :ok end @doc """ Shows an effect. """ @spec show_effect(t(), boolean(), String.t()) :: :ok def show_effect(_api, _broadcast, effect) do Logger.debug("Show effect: #{effect}") :ok end @doc """ Plays a sound. """ @spec play_sound(t(), boolean(), String.t()) :: :ok def play_sound(_api, _broadcast, sound) do Logger.debug("Play sound: #{sound}") :ok end @doc """ Changes background music. """ @spec change_music(t(), String.t()) :: :ok def change_music(_api, song_name) do Logger.debug("Change music: #{song_name}") :ok end @doc """ Gains NX (cash points). """ @spec gain_nx(t(), integer()) :: :ok def gain_nx(_api, amount) do Logger.debug("Gain NX: #{amount}") :ok end end