kimi gone wild
This commit is contained in:
782
lib/odinsea/shop/mts.ex
Normal file
782
lib/odinsea/shop/mts.ex
Normal file
@@ -0,0 +1,782 @@
|
||||
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
|
||||
Reference in New Issue
Block a user