port over some more

This commit is contained in:
ra
2026-02-14 23:58:01 -07:00
parent 0222be36c5
commit 61176cd416
107 changed files with 9124 additions and 375 deletions

View File

@@ -94,6 +94,10 @@ defmodule Odinsea.Game.Character do
:face,
# GM Level (0 = normal player, >0 = GM)
:gm,
# Guild
:guild_id,
:guild_rank,
:alliance_rank,
# Stats
:stats,
# Position & Map
@@ -134,6 +138,9 @@ defmodule Odinsea.Game.Character do
hair: non_neg_integer(),
face: non_neg_integer(),
gm: non_neg_integer(),
guild_id: non_neg_integer(),
guild_rank: non_neg_integer(),
alliance_rank: non_neg_integer(),
stats: Stats.t(),
map_id: non_neg_integer(),
position: Position.t(),
@@ -281,6 +288,30 @@ defmodule Odinsea.Game.Character do
GenServer.call(via_tuple(character_id), {:drop_item, inv_type, position, quantity})
end
@doc """
Adds meso to the character.
Returns {:ok, new_meso} on success, {:error, reason} on failure.
"""
def gain_meso(character_id, amount, show_in_chat \\ false) do
GenServer.call(via_tuple(character_id), {:gain_meso, amount, show_in_chat})
end
@doc """
Checks if the character has inventory space for an item.
Returns {:ok, slot} with the next free slot, or {:error, :inventory_full}.
"""
def check_inventory_space(character_id, inv_type, quantity \\ 1) do
GenServer.call(via_tuple(character_id), {:check_inventory_space, inv_type, quantity})
end
@doc """
Adds an item to the character's inventory (from a drop).
Returns {:ok, item} on success, {:error, reason} on failure.
"""
def add_item_from_drop(character_id, item) do
GenServer.call(via_tuple(character_id), {:add_item_from_drop, item})
end
# ============================================================================
# GenServer Callbacks
# ============================================================================
@@ -423,6 +454,45 @@ defmodule Odinsea.Game.Character do
end
end
@impl true
def handle_call({:gain_meso, amount, show_in_chat}, _from, state) do
# Cap meso at 9,999,999,999 (MapleStory max)
max_meso = 9_999_999_999
new_meso = min(state.meso + amount, max_meso)
new_state = %{state | meso: new_meso, updated_at: DateTime.utc_now()}
# TODO: Send meso gain packet to client if show_in_chat is true
{:reply, {:ok, new_meso}, new_state}
end
@impl true
def handle_call({:check_inventory_space, inv_type, _quantity}, _from, state) do
inventory = Map.get(state.inventories, inv_type, Inventory.new(inv_type))
case Inventory.get_next_free_slot(inventory) do
nil -> {:reply, {:error, :inventory_full}, state}
slot -> {:reply, {:ok, slot}, state}
end
end
@impl true
def handle_call({:add_item_from_drop, item}, _from, state) do
inv_type = get_inventory_type_from_item_id(item.item_id)
inventory = Map.get(state.inventories, inv_type, Inventory.new(inv_type))
case Inventory.add_item(inventory, item) do
{:ok, new_inventory, assigned_item} ->
new_inventories = Map.put(state.inventories, inv_type, new_inventory)
new_state = %{state | inventories: new_inventories, updated_at: DateTime.utc_now()}
{:reply, {:ok, assigned_item}, new_state}
{:error, reason} ->
{:reply, {:error, reason}, state}
end
end
@impl true
def handle_cast({:update_position, position}, state) do
new_state = %{
@@ -459,6 +529,19 @@ defmodule Odinsea.Game.Character do
{:via, Registry, {Odinsea.CharacterRegistry, character_id}}
end
defp get_inventory_type_from_item_id(item_id) do
type_prefix = div(item_id, 1_000_000)
case type_prefix do
1 -> :equip
2 -> :use
3 -> :setup
4 -> :etc
5 -> :cash
_ -> :etc
end
end
@doc """
Converts database character to in-game state.
"""
@@ -517,6 +600,9 @@ defmodule Odinsea.Game.Character do
hair: db_char.hair,
face: db_char.face,
gm: db_char.gm,
guild_id: db_char.guild_id || 0,
guild_rank: db_char.guild_rank || 0,
alliance_rank: db_char.alliance_rank || 0,
stats: stats,
map_id: db_char.map_id,
position: position,
@@ -850,4 +936,112 @@ defmodule Odinsea.Game.Character do
# TODO: Use actual MapleStory EXP table
level * level * level + 100 * level
end
# ============================================================================
# Scripting API Helper Functions
# ============================================================================
@doc """
Gets the character's channel ID.
"""
def get_channel(character_id) do
case get_state(character_id) do
nil -> {:error, :character_not_found}
%State{channel_id: channel_id} -> {:ok, channel_id}
end
end
@doc """
Updates the character's meso.
"""
def update_meso(character_id, new_meso) do
GenServer.cast(via_tuple(character_id), {:update_meso, new_meso})
end
@doc """
Updates the character's job.
"""
def update_job(character_id, new_job) do
GenServer.cast(via_tuple(character_id), {:update_job, new_job})
end
@doc """
Updates a skill level.
"""
def update_skill(character_id, skill_id, level, master_level) do
GenServer.cast(via_tuple(character_id), {:update_skill, skill_id, level, master_level})
end
@doc """
Adds an item to inventory.
"""
def add_item(character_id, inventory_type, item) do
GenServer.call(via_tuple(character_id), {:add_item, inventory_type, item})
end
@doc """
Removes items by item ID.
"""
def remove_item_by_id(character_id, item_id, quantity) do
GenServer.call(via_tuple(character_id), {:remove_item_by_id, item_id, quantity})
end
# ============================================================================
# GenServer Callbacks - Scripting Operations
# ============================================================================
@impl true
def handle_cast({:update_meso, new_meso}, state) do
new_state = %{state | meso: new_meso, updated_at: DateTime.utc_now()}
{:noreply, new_state}
end
@impl true
def handle_cast({:update_job, new_job}, state) do
new_state = %{state | job: new_job, updated_at: DateTime.utc_now()}
{:noreply, new_state}
end
@impl true
def handle_cast({:update_skill, skill_id, level, master_level}, state) do
skill_entry = %{
level: level,
master_level: master_level,
expiration: -1
}
new_skills = Map.put(state.skills, skill_id, skill_entry)
new_state = %{state | skills: new_skills, updated_at: DateTime.utc_now()}
{:noreply, new_state}
end
@impl true
def handle_call({:add_item, inventory_type, item}, _from, state) do
inventory = Map.get(state.inventories, inventory_type, Inventory.new(inventory_type))
case Inventory.add_item(inventory, item) do
{:ok, new_inventory} ->
new_inventories = Map.put(state.inventories, inventory_type, new_inventory)
new_state = %{state | inventories: new_inventories, updated_at: DateTime.utc_now()}
{:reply, :ok, new_state}
{:error, reason} ->
{:reply, {:error, reason}, state}
end
end
@impl true
def handle_call({:remove_item_by_id, item_id, quantity}, _from, state) do
inventory_type = Inventory.get_type_by_item_id(item_id)
inventory = Map.get(state.inventories, inventory_type, Inventory.new(inventory_type))
case Inventory.remove_by_id(inventory, item_id, quantity) do
{:ok, new_inventory, _removed} ->
new_inventories = Map.put(state.inventories, inventory_type, new_inventory)
new_state = %{state | inventories: new_inventories, updated_at: DateTime.utc_now()}
{:reply, :ok, new_state}
{:error, reason} ->
{:reply, {:error, reason}, state}
end
end
end