Files
2026-02-14 19:36:59 -07:00

191 lines
4.7 KiB
Elixir

defmodule Odinsea.Game.Shop do
@moduledoc """
Manages NPC shops: buying, selling, and recharging items.
Ported from src/server/MapleShop.java
TODO: Full implementation requires:
- Shop item database loading
- Inventory system integration
- Item information provider
- Price calculations
- Buyback system
"""
use GenServer
require Logger
# Shop item structure
defmodule ShopItem do
@moduledoc """
Represents an item in a shop.
"""
defstruct [
:item_id,
:price,
:pitch,
:quantity,
:max_per_slot,
:req_item,
:req_item_q,
:category,
:rank
]
end
# Rechargeable items (throwing stars and bullets)
@rechargeable_items MapSet.new([
# Throwing stars
2_070_000,
2_070_001,
2_070_002,
2_070_003,
2_070_004,
2_070_005,
2_070_006,
2_070_007,
2_070_008,
2_070_009,
2_070_010,
2_070_011,
2_070_012,
2_070_013,
2_070_016,
2_070_018,
2_070_019,
2_070_023,
2_070_024,
# Bullets
2_330_000,
2_330_001,
2_330_002,
2_330_003,
2_330_004,
2_330_005,
2_330_007,
2_330_008,
2_331_000,
2_332_000
])
# GenServer client API
def start_link(opts) do
GenServer.start_link(__MODULE__, opts)
end
@doc """
Load a shop by NPC ID.
"""
def load_shop(npc_id) do
# TODO: Load shop from database
# For now, return a stub shop
{:ok, %{id: npc_id, npc_id: npc_id, items: []}}
end
@doc """
Send shop to client.
"""
def send_shop(client_pid, shop, npc_id) do
# TODO: Send shop packet to client
# Should encode shop items and send OPEN_NPC_SHOP packet
Logger.debug("Sending shop #{shop.id} to client #{inspect(client_pid)} (STUB)")
:ok
end
@doc """
Handle buying an item from shop.
"""
def buy_item(client_pid, shop, item_id, quantity) do
# TODO: Full buy implementation:
# 1. Find shop item by item_id
# 2. Validate mount item for job (if mount)
# 3. Check inventory space
# 4. Check mesos or required item
# 5. Deduct cost and give item
# 6. Send confirmation packet
Logger.debug("Shop buy: item=#{item_id}, qty=#{quantity} (STUB)")
:ok
end
@doc """
Handle selling an item to shop.
"""
def sell_item(client_pid, shop, inv_type, slot, quantity) do
# TODO: Full sell implementation:
# 1. Get item from inventory
# 2. Validate item can be sold (not pet, not expiring, etc.)
# 3. Add to buyback (if applicable)
# 4. Remove item from inventory
# 5. Calculate sell price
# 6. Give mesos to player
# 7. Send confirmation packet
Logger.debug("Shop sell: type=#{inv_type}, slot=#{slot}, qty=#{quantity} (STUB)")
:ok
end
@doc """
Handle recharging throwing stars or bullets.
"""
def recharge_item(client_pid, shop, slot) do
# TODO: Full recharge implementation:
# 1. Get item from USE inventory
# 2. Validate item is rechargeable (stars/bullets)
# 3. Validate shop sells this item
# 4. Calculate recharge cost
# 5. Check mesos
# 6. Recharge to full quantity
# 7. Send confirmation packet
Logger.debug("Shop recharge: slot=#{slot} (STUB)")
:ok
end
@doc """
Check if an item is rechargeable.
"""
def rechargeable?(item_id) do
MapSet.member?(@rechargeable_items, item_id)
end
@doc """
Find a shop item by item ID.
"""
def find_shop_item(shop, item_id) do
Enum.find(shop.items, fn item -> item.item_id == item_id end)
end
# GenServer callbacks
@impl true
def init(opts) do
shop_id = Keyword.fetch!(opts, :shop_id)
npc_id = Keyword.get(opts, :npc_id, shop_id)
state = %{
id: shop_id,
npc_id: npc_id,
items: []
}
{:ok, state}
end
@impl true
def handle_call({:add_item, shop_item}, _from, state) do
new_items = [shop_item | state.items]
{:reply, :ok, %{state | items: new_items}}
end
@impl true
def handle_call(:get_items, _from, state) do
{:reply, state.items, state}
end
@impl true
def handle_call(:get_npc_id, _from, state) do
{:reply, state.npc_id, state}
end
end