Files
odinsea-elixir/lib/odinsea/scripting/player_api.ex
2026-02-14 23:12:33 -07:00

1494 lines
37 KiB
Elixir

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