783 lines
20 KiB
Elixir
783 lines
20 KiB
Elixir
defmodule Odinsea.Shop.MTS do
|
|
@moduledoc """
|
|
Maple Trading System (MTS) implementation.
|
|
|
|
The MTS allows players to:
|
|
- List items for sale (buy now)
|
|
- Purchase items from other players
|
|
- Search for items
|
|
- Manage their MTS cart
|
|
|
|
Ported from handling/cashshop/handler/MTSOperation.java
|
|
and server/MTSStorage.java / server/MTSCart.java
|
|
"""
|
|
|
|
use GenServer
|
|
require Logger
|
|
|
|
alias Odinsea.Game.Inventory
|
|
|
|
# MTS opcodes
|
|
@mts_sell 2
|
|
@mts_page 5
|
|
@mts_search 6
|
|
@mts_cancel 7
|
|
@mts_transfer 8
|
|
@mts_add_cart 9
|
|
@mts_del_cart 10
|
|
@mts_buy_now 16
|
|
@mts_buy_cart 17
|
|
|
|
# MTS Constants
|
|
@min_price 100
|
|
@mts_meso 5000
|
|
@listing_duration_days 7
|
|
|
|
# ETS tables
|
|
@mts_items :odinsea_mts_items
|
|
@mts_carts :odinsea_mts_carts
|
|
|
|
defstruct [
|
|
:id,
|
|
:item,
|
|
:price,
|
|
:seller_id,
|
|
:seller_name,
|
|
:expiration,
|
|
:buyer_id
|
|
]
|
|
|
|
## Public API
|
|
|
|
def start_link(opts \\ []) do
|
|
GenServer.start_link(__MODULE__, opts, name: __MODULE__)
|
|
end
|
|
|
|
@doc """
|
|
Gets or creates a cart for a character.
|
|
"""
|
|
@spec get_cart(integer()) :: map()
|
|
def get_cart(character_id) do
|
|
case :ets.lookup(@mts_carts, character_id) do
|
|
[{^character_id, cart}] ->
|
|
cart
|
|
|
|
[] ->
|
|
cart = %{
|
|
character_id: character_id,
|
|
cart: [],
|
|
inventory: [],
|
|
not_yet_sold: [],
|
|
owed_nx: 0,
|
|
tab: 0,
|
|
page: 0,
|
|
type: 0,
|
|
current_view: []
|
|
}
|
|
|
|
:ets.insert(@mts_carts, {character_id, cart})
|
|
cart
|
|
end
|
|
end
|
|
|
|
@doc """
|
|
Updates cart view settings.
|
|
"""
|
|
@spec change_cart_info(integer(), integer(), integer(), integer()) :: :ok
|
|
def change_cart_info(character_id, tab, page, type) do
|
|
cart = get_cart(character_id)
|
|
|
|
new_cart = %{
|
|
cart
|
|
| tab: tab,
|
|
page: page,
|
|
type: type
|
|
}
|
|
|
|
:ets.insert(@mts_carts, {character_id, new_cart})
|
|
:ok
|
|
end
|
|
|
|
@doc """
|
|
Updates current view (search results).
|
|
"""
|
|
@spec change_current_view(integer(), [map()]) :: :ok
|
|
def change_current_view(character_id, items) do
|
|
cart = get_cart(character_id)
|
|
new_cart = %{cart | current_view: items}
|
|
:ets.insert(@mts_carts, {character_id, new_cart})
|
|
:ok
|
|
end
|
|
|
|
@doc """
|
|
Lists an item for sale on the MTS.
|
|
"""
|
|
@spec list_item(integer(), map(), integer(), String.t()) ::
|
|
{:ok, integer()} | {:error, atom()}
|
|
def list_item(seller_id, item, price, seller_name) do
|
|
if price < @min_price do
|
|
{:error, :price_too_low}
|
|
else
|
|
expiration = Odinsea.now() + @listing_duration_days * 24 * 60 * 60 * 1000
|
|
|
|
listing = %__MODULE__{
|
|
id: generate_listing_id(),
|
|
item: item,
|
|
price: price,
|
|
seller_id: seller_id,
|
|
seller_name: seller_name,
|
|
expiration: expiration,
|
|
buyer_id: nil
|
|
}
|
|
|
|
:ets.insert(@mts_items, {listing.id, listing})
|
|
|
|
# Add to seller's "not yet sold" list
|
|
cart = get_cart(seller_id)
|
|
new_not_yet_sold = [listing.id | cart.not_yet_sold]
|
|
new_cart = %{cart | not_yet_sold: new_not_yet_sold}
|
|
:ets.insert(@mts_carts, {seller_id, new_cart})
|
|
|
|
{:ok, listing.id}
|
|
end
|
|
end
|
|
|
|
@doc """
|
|
Gets a single MTS item by ID.
|
|
"""
|
|
@spec get_item(integer()) :: map() | nil
|
|
def get_item(id) do
|
|
case :ets.lookup(@mts_items, id) do
|
|
[{^id, item}] -> item
|
|
[] -> nil
|
|
end
|
|
end
|
|
|
|
@doc """
|
|
Removes an item from the MTS.
|
|
Returns the item to the seller's transfer inventory if canceling.
|
|
"""
|
|
@spec remove_item(integer(), integer(), boolean()) :: boolean()
|
|
def remove_item(id, character_id, cancel) do
|
|
case get_item(id) do
|
|
nil ->
|
|
false
|
|
|
|
item ->
|
|
if item.seller_id != character_id do
|
|
false
|
|
else
|
|
:ets.delete(@mts_items, id)
|
|
|
|
if cancel do
|
|
# Return item to seller's transfer inventory
|
|
cart = get_cart(character_id)
|
|
new_inventory = [item.item | cart.inventory]
|
|
new_not_yet_sold = List.delete(cart.not_yet_sold, id)
|
|
|
|
new_cart = %{
|
|
cart
|
|
| inventory: new_inventory,
|
|
not_yet_sold: new_not_yet_sold
|
|
}
|
|
|
|
:ets.insert(@mts_carts, {character_id, new_cart})
|
|
end
|
|
|
|
true
|
|
end
|
|
end
|
|
end
|
|
|
|
@doc """
|
|
Buys an item from the MTS.
|
|
"""
|
|
@spec buy_item(integer(), integer(), integer()) ::
|
|
{:ok, map()} | {:error, atom()}
|
|
def buy_item(id, buyer_id, offered_price) do
|
|
case get_item(id) do
|
|
nil ->
|
|
{:error, :not_found}
|
|
|
|
item ->
|
|
if item.seller_id == buyer_id do
|
|
{:error, :own_item}
|
|
else
|
|
if offered_price < item.price do
|
|
{:error, :insufficient_funds}
|
|
else
|
|
# Mark as sold and transfer to buyer
|
|
:ets.delete(@mts_items, id)
|
|
|
|
# Add to buyer's transfer inventory
|
|
buyer_cart = get_cart(buyer_id)
|
|
new_buyer_inventory = [item.item | buyer_cart.inventory]
|
|
new_buyer_cart = %{buyer_cart | inventory: new_buyer_inventory}
|
|
:ets.insert(@mts_carts, {buyer_id, new_buyer_cart})
|
|
|
|
# Credit seller with NX
|
|
seller_cart = get_cart(item.seller_id)
|
|
new_owed = seller_cart.owed_nx + item.price
|
|
new_not_yet_sold = List.delete(seller_cart.not_yet_sold, id)
|
|
|
|
new_seller_cart = %{
|
|
seller_cart
|
|
| owed_nx: new_owed,
|
|
not_yet_sold: new_not_yet_sold
|
|
}
|
|
|
|
:ets.insert(@mts_carts, {item.seller_id, new_seller_cart})
|
|
|
|
{:ok, item}
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
@doc """
|
|
Searches for items in the MTS.
|
|
"""
|
|
@spec search(boolean(), String.t(), integer(), integer()) :: [map()]
|
|
def search(_cash_search, search_string, type, tab) do
|
|
# Get all items
|
|
all_items =
|
|
:ets.select(@mts_items, [{{:_, :"$1"}, [], [:"$1"]}])
|
|
|> Enum.filter(&is_nil(&1.buyer_id))
|
|
|
|
# Apply filters
|
|
items =
|
|
cond do
|
|
# Tab 0 = all items
|
|
tab == 0 ->
|
|
all_items
|
|
|
|
# Tab 1 = search by name
|
|
tab == 1 && search_string != "" ->
|
|
Enum.filter(all_items, fn item ->
|
|
item_name = get_item_name(item.item.item_id)
|
|
String.contains?(String.downcase(item_name), String.downcase(search_string))
|
|
end)
|
|
|
|
# Type filtering
|
|
type > 0 ->
|
|
Enum.filter(all_items, fn item ->
|
|
get_item_type(item.item.item_id) == type
|
|
end)
|
|
|
|
true ->
|
|
all_items
|
|
end
|
|
|
|
# Sort by newest first
|
|
Enum.sort_by(items, & &1.id, :desc)
|
|
end
|
|
|
|
@doc """
|
|
Adds an item to the cart.
|
|
"""
|
|
@spec add_to_cart(integer(), integer()) :: boolean()
|
|
def add_to_cart(character_id, item_id) do
|
|
cart = get_cart(character_id)
|
|
|
|
if item_id in cart.cart do
|
|
false
|
|
else
|
|
new_cart = %{cart | cart: [item_id | cart.cart]}
|
|
:ets.insert(@mts_carts, {character_id, new_cart})
|
|
true
|
|
end
|
|
end
|
|
|
|
@doc """
|
|
Removes an item from the cart.
|
|
"""
|
|
@spec remove_from_cart(integer(), integer()) :: boolean()
|
|
def remove_from_cart(character_id, item_id) do
|
|
cart = get_cart(character_id)
|
|
|
|
if item_id in cart.cart do
|
|
new_cart = %{cart | cart: List.delete(cart.cart, item_id)}
|
|
:ets.insert(@mts_carts, {character_id, new_cart})
|
|
true
|
|
else
|
|
false
|
|
end
|
|
end
|
|
|
|
@doc """
|
|
Transfers an item from MTS inventory to game inventory.
|
|
"""
|
|
@spec transfer_item(integer(), integer()) :: {:ok, map()} | {:error, atom()}
|
|
def transfer_item(character_id, index) do
|
|
cart = get_cart(character_id)
|
|
|
|
if index < 0 || index >= length(cart.inventory) do
|
|
{:error, :invalid_index}
|
|
else
|
|
item = Enum.at(cart.inventory, index)
|
|
new_inventory = List.delete_at(cart.inventory, index)
|
|
new_cart = %{cart | inventory: new_inventory}
|
|
:ets.insert(@mts_carts, {character_id, new_cart})
|
|
{:ok, item}
|
|
end
|
|
end
|
|
|
|
@doc """
|
|
Claims owed NX for a character.
|
|
"""
|
|
@spec claim_nx(integer()) :: integer()
|
|
def claim_nx(character_id) do
|
|
cart = get_cart(character_id)
|
|
owed = cart.owed_nx
|
|
|
|
if owed > 0 do
|
|
new_cart = %{cart | owed_nx: 0}
|
|
:ets.insert(@mts_carts, {character_id, new_cart})
|
|
end
|
|
|
|
owed
|
|
end
|
|
|
|
@doc """
|
|
Checks and removes expired listings.
|
|
"""
|
|
@spec check_expirations() :: :ok
|
|
def check_expirations do
|
|
now = Odinsea.now()
|
|
|
|
expired =
|
|
:ets.select(@mts_items, [{{:_, :"$1"}, [], [:"$1"]}])
|
|
|> Enum.filter(fn item -> item.expiration < now end)
|
|
|
|
Enum.each(expired, fn item ->
|
|
:ets.delete(@mts_items, item.id)
|
|
|
|
# Return item to seller
|
|
cart = get_cart(item.seller_id)
|
|
new_inventory = [item.item | cart.inventory]
|
|
new_not_yet_sold = List.delete(cart.not_yet_sold, item.id)
|
|
|
|
new_cart = %{
|
|
cart
|
|
| inventory: new_inventory,
|
|
not_yet_sold: new_not_yet_sold
|
|
}
|
|
|
|
:ets.insert(@mts_carts, {item.seller_id, new_cart})
|
|
end)
|
|
|
|
:ok
|
|
end
|
|
|
|
@doc """
|
|
Gets current MTS listings for display.
|
|
"""
|
|
@spec get_current_mts(map()) :: [map()]
|
|
def get_current_mts(cart) do
|
|
page_size = 16
|
|
start_idx = cart.page * page_size
|
|
|
|
items =
|
|
if cart.tab == 0 do
|
|
:ets.select(@mts_items, [{{:_, :"$1"}, [], [:"$1"]}])
|
|
else
|
|
cart.current_view
|
|
end
|
|
|
|
items
|
|
|> Enum.filter(&is_nil(&1.buyer_id))
|
|
|> Enum.slice(start_idx, page_size)
|
|
end
|
|
|
|
@doc """
|
|
Gets "not yet sold" listings for a character.
|
|
"""
|
|
@spec get_not_yet_sold(integer()) :: [map()]
|
|
def get_not_yet_sold(character_id) do
|
|
cart = get_cart(character_id)
|
|
|
|
Enum.map(cart.not_yet_sold, &get_item/1)
|
|
|> Enum.filter(&(&1 != nil))
|
|
end
|
|
|
|
@doc """
|
|
Gets transfer inventory for a character.
|
|
"""
|
|
@spec get_transfer(integer()) :: [map()]
|
|
def get_transfer(character_id) do
|
|
cart = get_cart(character_id)
|
|
cart.inventory
|
|
end
|
|
|
|
@doc """
|
|
Checks if an item is in the cart.
|
|
"""
|
|
@spec in_cart?(integer(), integer()) :: boolean()
|
|
def in_cart?(character_id, item_id) do
|
|
cart = get_cart(character_id)
|
|
item_id in cart.cart
|
|
end
|
|
|
|
@doc """
|
|
Handles MTS operation packets.
|
|
"""
|
|
@spec handle(In.t(), map()) :: map()
|
|
def handle(packet, client_state) do
|
|
if In.remaining(packet) == 0 do
|
|
# Empty packet - just refresh
|
|
send_mts_packets(client_state)
|
|
else
|
|
{op, packet} = In.decode_byte(packet)
|
|
handle_op(op, packet, client_state)
|
|
end
|
|
end
|
|
|
|
## GenServer Callbacks
|
|
|
|
@impl true
|
|
def init(_opts) do
|
|
:ets.new(@mts_items, [:set, :public, :named_table, read_concurrency: true])
|
|
:ets.new(@mts_carts, [:set, :public, :named_table])
|
|
|
|
# Schedule expiration check
|
|
schedule_expiration_check()
|
|
|
|
{:ok, %{}}
|
|
end
|
|
|
|
@impl true
|
|
def handle_info(:check_expirations, state) do
|
|
check_expirations()
|
|
schedule_expiration_check()
|
|
{:noreply, state}
|
|
end
|
|
|
|
## Private Functions
|
|
|
|
defp handle_op(@mts_sell, packet, client_state) do
|
|
{inv_type, packet} = In.decode_byte(packet)
|
|
{item_id, packet} = In.decode_int(packet)
|
|
{has_unique_id, packet} = In.decode_byte(packet)
|
|
|
|
if has_unique_id != 0 || (inv_type != 1 && inv_type != 2) do
|
|
send_mts_fail_sell(client_state)
|
|
client_state
|
|
else
|
|
# Parse item data from packet
|
|
{item_data, packet} = parse_item_data(packet, inv_type)
|
|
{price, _packet} = In.decode_int(packet)
|
|
|
|
character = client_state.character
|
|
|
|
# Validate item can be sold
|
|
with :ok <- validate_mts_item(character, item_id, item_data, inv_type),
|
|
:ok <- check_meso_fee(character),
|
|
true <- length(get_cart(character.id).not_yet_sold) < 10 do
|
|
# Create item copy
|
|
item = create_mts_item(character, item_id, item_data)
|
|
|
|
# List on MTS
|
|
{:ok, _listing_id} = list_item(character.id, item, price, character.name)
|
|
|
|
# Deduct meso and remove from inventory
|
|
new_character = deduct_meso(character, @mts_meso)
|
|
new_character = remove_from_inventory(new_character, inv_type, item_data.slot)
|
|
|
|
client_state
|
|
|> Map.put(:character, new_character)
|
|
|> send_mts_confirm_sell()
|
|
else
|
|
_ ->
|
|
send_mts_fail_sell(client_state)
|
|
client_state
|
|
end
|
|
end
|
|
end
|
|
|
|
defp handle_op(@mts_page, packet, client_state) do
|
|
{tab, packet} = In.decode_int(packet)
|
|
{page, packet} = In.decode_int(packet)
|
|
{type, _packet} = In.decode_int(packet)
|
|
|
|
change_cart_info(client_state.character.id, tab, page, type)
|
|
send_mts_packets(client_state)
|
|
end
|
|
|
|
defp handle_op(@mts_search, packet, client_state) do
|
|
{tab, packet} = In.decode_int(packet)
|
|
{page, packet} = In.decode_int(packet)
|
|
{_zero, packet} = In.decode_int(packet)
|
|
{cash_search, packet} = In.decode_int(packet)
|
|
{search_string, _packet} = In.decode_string(packet)
|
|
|
|
cart = get_cart(client_state.character.id)
|
|
change_cart_info(client_state.character.id, tab, page, cart.type)
|
|
|
|
# Perform search
|
|
results = search(cash_search > 0, search_string, cart.type, tab)
|
|
change_current_view(client_state.character.id, results)
|
|
|
|
send_mts_packets(client_state)
|
|
end
|
|
|
|
defp handle_op(@mts_cancel, packet, client_state) do
|
|
{id, _packet} = In.decode_int(packet)
|
|
|
|
if remove_item(id, client_state.character.id, true) do
|
|
send_mts_confirm_cancel(client_state)
|
|
send_mts_packets(client_state)
|
|
else
|
|
send_mts_fail_cancel(client_state)
|
|
client_state
|
|
end
|
|
end
|
|
|
|
defp handle_op(@mts_transfer, packet, client_state) do
|
|
# Fake ID encoding
|
|
{fake_id, _packet} = In.decode_int(packet)
|
|
index = Integer.pow(2, 31) - 1 - fake_id
|
|
|
|
case transfer_item(client_state.character.id, index) do
|
|
{:ok, item} ->
|
|
# Add to inventory
|
|
case add_to_inventory(client_state.character, item) do
|
|
{:ok, new_character, position} ->
|
|
client_state
|
|
|> Map.put(:character, new_character)
|
|
|> send_mts_confirm_transfer(item, position)
|
|
|> send_mts_packets()
|
|
|
|
{:error, _} ->
|
|
send_mts_fail_buy(client_state)
|
|
client_state
|
|
end
|
|
|
|
{:error, _} ->
|
|
send_mts_fail_buy(client_state)
|
|
client_state
|
|
end
|
|
end
|
|
|
|
defp handle_op(@mts_add_cart, packet, client_state) do
|
|
{id, _packet} = In.decode_int(packet)
|
|
|
|
if in_cart?(client_state.character.id, id) do
|
|
send_cart_message(client_state, true, false)
|
|
else
|
|
if add_to_cart(client_state.character.id, id) do
|
|
send_cart_message(client_state, false, false)
|
|
else
|
|
send_cart_message(client_state, true, false)
|
|
end
|
|
end
|
|
|
|
client_state
|
|
end
|
|
|
|
defp handle_op(@mts_del_cart, packet, client_state) do
|
|
{id, _packet} = In.decode_int(packet)
|
|
|
|
if remove_from_cart(client_state.character.id, id) do
|
|
send_cart_message(client_state, false, true)
|
|
else
|
|
send_cart_message(client_state, true, true)
|
|
end
|
|
|
|
client_state
|
|
end
|
|
|
|
defp handle_op(op, packet, client_state) when op in [@mts_buy_now, @mts_buy_cart] do
|
|
{id, _packet} = In.decode_int(packet)
|
|
|
|
case get_item(id) do
|
|
nil ->
|
|
send_mts_fail_buy(client_state)
|
|
client_state
|
|
|
|
item ->
|
|
if item.seller_id == client_state.character.id do
|
|
send_mts_fail_buy(client_state)
|
|
client_state
|
|
else
|
|
# Check buyer has enough NX
|
|
character = client_state.character
|
|
|
|
if (character.nx_cash || 0) >= item.price do
|
|
case buy_item(id, character.id, item.price) do
|
|
{:ok, _} ->
|
|
# Deduct NX
|
|
new_character = %{character | nx_cash: character.nx_cash - item.price}
|
|
|
|
client_state
|
|
|> Map.put(:character, new_character)
|
|
|> send_mts_confirm_buy()
|
|
|> send_mts_packets()
|
|
|
|
{:error, _} ->
|
|
send_mts_fail_buy(client_state)
|
|
client_state
|
|
end
|
|
else
|
|
send_mts_fail_buy(client_state)
|
|
client_state
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
defp handle_op(_op, _packet, client_state) do
|
|
# Unknown op - just refresh
|
|
send_mts_packets(client_state)
|
|
end
|
|
|
|
defp parse_item_data(packet, 1) do
|
|
# Equipment item data
|
|
packet = In.skip(packet, 32) # Skip various stats
|
|
{_owner, packet} = In.decode_string(packet)
|
|
packet = In.skip(packet, 50)
|
|
{slot, packet} = In.decode_int(packet)
|
|
packet = In.skip(packet, 4)
|
|
|
|
{%{slot: slot}, packet}
|
|
end
|
|
|
|
defp parse_item_data(packet, 2) do
|
|
# Regular item data
|
|
{stars, packet} = In.decode_short(packet)
|
|
{_owner, packet} = In.decode_string(packet)
|
|
packet = In.skip(packet, 2) # Flag
|
|
{slot, packet} = In.decode_int(packet)
|
|
{quantity, _packet} = In.decode_int(packet)
|
|
|
|
{%{slot: slot, quantity: quantity, stars: stars}, packet}
|
|
end
|
|
|
|
defp validate_mts_item(character, item_id, item_data, inv_type) do
|
|
# Check item exists in inventory
|
|
inventory = Map.get(character.inventories, inv_type, [])
|
|
|
|
case Enum.find(inventory, &(&1.position == item_data.slot)) do
|
|
nil ->
|
|
:error
|
|
|
|
item ->
|
|
if item.item_id == item_id && item.quantity >= (item_data.quantity || 1) do
|
|
:ok
|
|
else
|
|
:error
|
|
end
|
|
end
|
|
end
|
|
|
|
defp check_meso_fee(character) do
|
|
if character.meso >= @mts_meso do
|
|
:ok
|
|
else
|
|
:error
|
|
end
|
|
end
|
|
|
|
defp create_mts_item(character, item_id, item_data) do
|
|
%{
|
|
item_id: item_id,
|
|
quantity: item_data.quantity || 1,
|
|
owner: character.name,
|
|
flag: 0
|
|
}
|
|
end
|
|
|
|
defp deduct_meso(character, amount) do
|
|
%{character | meso: character.meso - amount}
|
|
end
|
|
|
|
defp remove_from_inventory(character, inv_type, slot) do
|
|
inventory = Map.get(character.inventories, inv_type, [])
|
|
new_inventory = Enum.reject(inventory, &(&1.position == slot))
|
|
inventories = Map.put(character.inventories, inv_type, new_inventory)
|
|
%{character | inventories: inventories}
|
|
end
|
|
|
|
defp add_to_inventory(character, item) do
|
|
inv_type = Odinsea.Game.InventoryType.from_item_id(item.item_id)
|
|
|
|
if Odinsea.Shop.Operation.check_inventory_space(character, item.item_id, item.quantity) == :ok do
|
|
inventory = Map.get(character.inventories, inv_type, [])
|
|
position = Inventory.next_free_slot(inventory)
|
|
new_item = %{item | position: position}
|
|
new_inventory = [new_item | inventory]
|
|
inventories = Map.put(character.inventories, inv_type, new_inventory)
|
|
{:ok, %{character | inventories: inventories}, position}
|
|
else
|
|
{:error, :no_space}
|
|
end
|
|
end
|
|
|
|
defp get_item_name(item_id) do
|
|
Odinsea.Game.ItemInfo.get_name(item_id) || "Unknown"
|
|
end
|
|
|
|
defp get_item_type(item_id) do
|
|
cond do
|
|
item_id >= 1_000_000 && item_id < 2_000_000 -> 1
|
|
item_id >= 2_000_000 && item_id < 3_000_000 -> 2
|
|
item_id >= 4_000_000 && item_id < 5_000_000 -> 4
|
|
true -> 0
|
|
end
|
|
end
|
|
|
|
defp generate_listing_id do
|
|
:erlang.unique_integer([:positive])
|
|
end
|
|
|
|
defp schedule_expiration_check do
|
|
# Check every hour
|
|
Process.send_after(self(), :check_expirations, 60 * 60 * 1000)
|
|
end
|
|
|
|
# Packet senders
|
|
defp send_mts_packets(client_state) do
|
|
cart = get_cart(client_state.character.id)
|
|
|
|
Odinsea.Shop.Packets.send_current_mts(client_state.socket, cart)
|
|
Odinsea.Shop.Packets.send_not_yet_sold(client_state.socket, cart)
|
|
Odinsea.Shop.Packets.send_transfer(client_state.socket, cart)
|
|
Odinsea.Shop.Packets.show_mts_cash(client_state.socket, client_state.character)
|
|
Odinsea.Shop.Packets.enable_cs_use(client_state.socket)
|
|
|
|
client_state
|
|
end
|
|
|
|
defp send_mts_fail_sell(client_state) do
|
|
Odinsea.Shop.Packets.get_mts_fail_sell(client_state.socket)
|
|
end
|
|
|
|
defp send_mts_confirm_sell(client_state) do
|
|
Odinsea.Shop.Packets.get_mts_confirm_sell(client_state.socket)
|
|
end
|
|
|
|
defp send_mts_fail_cancel(client_state) do
|
|
Odinsea.Shop.Packets.get_mts_fail_cancel(client_state.socket)
|
|
end
|
|
|
|
defp send_mts_confirm_cancel(client_state) do
|
|
Odinsea.Shop.Packets.get_mts_confirm_cancel(client_state.socket)
|
|
end
|
|
|
|
defp send_mts_fail_buy(client_state) do
|
|
Odinsea.Shop.Packets.get_mts_fail_buy(client_state.socket)
|
|
end
|
|
|
|
defp send_mts_confirm_buy(client_state) do
|
|
Odinsea.Shop.Packets.get_mts_confirm_buy(client_state.socket)
|
|
end
|
|
|
|
defp send_mts_confirm_transfer(client_state, _item, position) do
|
|
# This needs the inventory type encoded
|
|
Odinsea.Shop.Packets.get_mts_confirm_transfer(client_state.socket, 1, position)
|
|
end
|
|
|
|
defp send_cart_message(client_state, failed, deleted) do
|
|
Odinsea.Shop.Packets.add_to_cart_message(client_state.socket, failed, deleted)
|
|
end
|
|
end
|