kimi gone wild

This commit is contained in:
ra
2026-02-14 23:12:33 -07:00
parent bbd205ecbe
commit 0222be36c5
98 changed files with 39726 additions and 309 deletions

View File

@@ -0,0 +1,923 @@
defmodule Odinsea.Shop.Operation do
@moduledoc """
Cash Shop Operation handlers.
Implements all cash shop functionality:
- Buying items with NX/Maple Points
- Gifting items to other players
- Wish list management
- Coupon redemption
- Inventory slot expansion
- Storage slot expansion
- Character slot expansion
Ported from handling/cashshop/handler/CashShopOperation.java
"""
require Logger
alias Odinsea.Shop.{CashItem, CashItemFactory, Packets}
alias Odinsea.Game.{Inventory, InventoryType, ItemInfo}
alias Odinsea.Database.Context
alias Odinsea.Net.Packet.In
# Cash shop action codes from Java
@action_coupon 0
@action_buy 3
@action_gift 4
@action_wishlist 5
@action_expand_inv 6
@action_expand_storage 7
@action_expand_chars 8
@action_to_inv 14
@action_to_cash_inv 15
@action_buy_friendship_ring 30
@action_buy_package 32
@action_buy_quest 34
@action_redeem 45
# Error codes
@error_none 0
@error_no_coupon 0xA5
@error_used_coupon 0xA7
@error_invalid_gender 0xA6
@error_no_space 0xB1
@error_invalid_target 0xA2
@error_same_account 0xA3
@error_invalid_slot 0xA4
@error_not_enough_meso 0xB8
@error_invalid_couple 0xA1
@error_invalid_ring 0xB4
@doc """
Handles a cash shop operation packet.
"""
@spec handle(In.t(), map()) :: map()
def handle(packet, client_state) do
{action, packet} = In.decode_byte(packet)
handle_action(action, packet, client_state)
end
# Coupon code redemption
defp handle_action(@action_coupon, packet, client_state) do
packet = In.skip(packet, 2)
{code, _packet} = In.decode_string(packet)
redeem_coupon(code, client_state)
end
# Buy item
defp handle_action(@action_buy, packet, client_state) do
packet = In.skip(packet, 1)
# toCharge = 1 for NX, 2 for Maple Points
{to_charge, packet} = In.decode_int(packet)
{sn, _packet} = In.decode_int(packet)
buy_item(sn, to_charge, client_state)
end
# Gift item
defp handle_action(@action_gift, packet, client_state) do
# Skip separator string
{_sep, packet} = In.decode_string(packet)
{sn, packet} = In.decode_int(packet)
{partner_name, packet} = In.decode_string(packet)
{msg, _packet} = In.decode_string(packet)
gift_item(sn, partner_name, msg, client_state)
end
# Wish list
defp handle_action(@action_wishlist, packet, client_state) do
# Read 10 wishlist items
wishlist =
Enum.reduce(1..10, {[], packet}, fn _, {list, pkt} ->
{sn, new_pkt} = In.decode_int(pkt)
{[sn | list], new_pkt}
end)
|> elem(0)
|> Enum.reverse()
update_wishlist(wishlist, client_state)
end
# Expand inventory
defp handle_action(@action_expand_inv, packet, client_state) do
packet = In.skip(packet, 1)
{to_charge, packet} = In.decode_int(packet)
{use_coupon, packet} = In.decode_byte(packet)
if use_coupon > 0 do
{sn, _packet} = In.decode_int(packet)
expand_inventory_coupon(sn, to_charge, client_state)
else
{inv_type, _packet} = In.decode_byte(packet)
expand_inventory(inv_type, to_charge, client_state)
end
end
# Expand storage
defp handle_action(@action_expand_storage, packet, client_state) do
packet = In.skip(packet, 1)
{to_charge, packet} = In.decode_int(packet)
{coupon, _packet} = In.decode_byte(packet)
slots = if coupon > 0, do: 8, else: 4
cost = if coupon > 0, do: 8_000, else: 4_000
expand_storage(slots, cost * div(slots, 4), to_charge, client_state)
end
# Expand character slots
defp handle_action(@action_expand_chars, packet, client_state) do
packet = In.skip(packet, 1)
{to_charge, packet} = In.decode_int(packet)
{sn, _packet} = In.decode_int(packet)
expand_character_slots(sn, to_charge, client_state)
end
# Move item from cash inventory to regular inventory
defp handle_action(@action_to_inv, packet, client_state) do
{cash_id, _packet} = In.decode_long(packet)
move_to_inventory(cash_id, client_state)
end
# Move item from regular inventory to cash inventory
defp handle_action(@action_to_cash_inv, packet, client_state) do
{unique_id, packet} = In.decode_long(packet)
{inv_type, _packet} = In.decode_byte(packet)
move_to_cash_inventory(unique_id, inv_type, client_state)
end
# Buy friendship/crush ring
defp handle_action(@action_buy_friendship_ring, packet, client_state) do
{_sep, packet} = In.decode_string(packet)
{to_charge, packet} = In.decode_int(packet)
{sn, packet} = In.decode_int(packet)
{partner_name, packet} = In.decode_string(packet)
{msg, _packet} = In.decode_string(packet)
buy_ring(sn, partner_name, msg, to_charge, client_state)
end
# Buy package
defp handle_action(@action_buy_package, packet, client_state) do
packet = In.skip(packet, 1)
{to_charge, packet} = In.decode_int(packet)
{sn, _packet} = In.decode_int(packet)
buy_package(sn, to_charge, client_state)
end
# Buy quest item (with meso)
defp handle_action(@action_buy_quest, packet, client_state) do
{sn, _packet} = In.decode_int(packet)
buy_quest_item(sn, client_state)
end
# Redeem code
defp handle_action(@action_redeem, _packet, client_state) do
send_redeem_response(client_state)
end
# Unknown action
defp handle_action(action, _packet, client_state) do
Logger.warning("Unknown cash shop action: #{action}")
send_error(@error_none, client_state)
end
# ==============================================================================
# CS Update (Initial Setup)
# ==============================================================================
@doc """
Sends initial cash shop update packets.
Called when player enters the cash shop.
"""
@spec cs_update(port(), map()) :: :ok
def cs_update(socket, character) do
Packets.get_cs_inventory(socket, character)
Packets.show_nx_maple_tokens(socket, character)
Packets.enable_cs_use(socket)
:ok
end
# ==============================================================================
# Implementation Functions
# ==============================================================================
@doc """
Buys a cash item for the player.
"""
@spec buy_item(integer(), integer(), map()) :: map()
def buy_item(sn, to_charge, client_state) do
character = client_state.character
with {:ok, item} <- validate_cash_item(sn),
:ok <- check_gender(item, character),
:ok <- check_cash_inventory_space(character),
:ok <- check_blocked_item(item),
:ok <- check_cash_balance(character, to_charge, item.price) do
# Deduct NX/Maple Points
new_character = modify_cs_points(character, to_charge, -item.price)
# Create item in cash inventory
cash_item = create_cash_item(item, "")
new_character = add_to_cash_inventory(new_character, cash_item)
# Send success packet
client_state
|> Map.put(:character, new_character)
|> send_bought_item(cash_item, sn)
else
{:error, code} -> send_error(code, client_state)
end
end
@doc """
Gifts an item to another player.
"""
@spec gift_item(integer(), String.t(), String.t(), map()) :: map()
def gift_item(sn, partner_name, msg, client_state) do
character = client_state.character
with {:ok, item} <- validate_cash_item(sn),
:ok <- validate_gift_message(msg),
:ok <- check_cash_balance(character, 1, item.price),
{:ok, target} <- find_character_by_name(partner_name),
:ok <- validate_gift_target(character, target),
:ok <- check_gender(item, target) do
# Deduct NX
new_character = modify_cs_points(character, 1, -item.price)
# Create gift record
create_gift(target.id, character.name, msg, item)
# Send success packet
client_state
|> Map.put(:character, new_character)
|> send_gift_sent(item, partner_name)
else
{:error, code} -> send_error(code, client_state)
end
end
@doc """
Redeems a coupon code.
"""
@spec redeem_coupon(String.t(), map()) :: map()
def redeem_coupon(code, client_state) do
if code == "" do
send_error(@error_none, client_state)
else
# Check coupon in database
case Context.get_coupon_info(code) do
{:ok, %{used: false, type: type, value: value}} ->
# Mark coupon as used
Context.mark_coupon_used(code, client_state.character.name)
# Apply coupon reward
apply_coupon_reward(type, value, client_state)
{:ok, %{used: true}} ->
send_error(@error_used_coupon, client_state)
_ ->
send_error(@error_no_coupon, client_state)
end
end
end
@doc """
Updates the player's wishlist.
"""
@spec update_wishlist([integer()], map()) :: map()
def update_wishlist(wishlist, client_state) do
# Validate all items exist
valid_items =
Enum.filter(wishlist, fn sn ->
CashItemFactory.get_item(sn) != nil
end)
|> Enum.take(10)
|> pad_wishlist()
# Update character wishlist
new_character = %{client_state.character | wishlist: valid_items}
client_state
|> Map.put(:character, new_character)
|> send_wishlist(valid_items)
end
@doc """
Expands inventory slots.
"""
@spec expand_inventory(integer(), integer(), map()) :: map()
def expand_inventory(inv_type, to_charge, client_state) do
character = client_state.character
cost = 4_000
with :ok <- check_cash_balance(character, to_charge, cost),
{:ok, inventory_type} <- get_inventory_type(inv_type),
:ok <- check_slot_limit(character, inventory_type) do
# Deduct NX
new_character = modify_cs_points(character, to_charge, -cost)
# Add slots (max 96)
slots_to_add = min(96 - get_current_slots(new_character, inventory_type), 4)
new_character = add_inventory_slots(new_character, inventory_type, slots_to_add)
send_inventory_expanded(client_state, inventory_type, slots_to_add)
else
{:error, code} -> send_error(code, client_state)
end
end
@doc """
Expands inventory using a coupon item.
"""
@spec expand_inventory_coupon(integer(), integer(), map()) :: map()
def expand_inventory_coupon(sn, to_charge, client_state) do
character = client_state.character
with {:ok, item} <- validate_cash_item(sn),
:ok <- check_cash_balance(character, to_charge, item.price),
{:ok, inventory_type} <- get_inventory_type_from_item(item),
:ok <- check_slot_limit(character, inventory_type) do
# Deduct NX
new_character = modify_cs_points(character, to_charge, -item.price)
# Add slots
slots_to_add = min(96 - get_current_slots(new_character, inventory_type), 8)
new_character = add_inventory_slots(new_character, inventory_type, slots_to_add)
send_inventory_expanded(client_state, inventory_type, slots_to_add)
else
{:error, code} -> send_error(code, client_state)
end
end
@doc """
Expands storage slots.
"""
@spec expand_storage(integer(), integer(), integer(), map()) :: map()
def expand_storage(slots, cost, to_charge, client_state) do
character = client_state.character
current_slots = character.storage_slots || 4
max_slots = 49 - slots
if current_slots >= max_slots do
send_error(@error_invalid_slot, client_state)
else
with :ok <- check_cash_balance(character, to_charge, cost) do
# Deduct NX
new_character = modify_cs_points(character, to_charge, -cost)
# Add slots
new_slots = min(current_slots + slots, max_slots)
new_character = %{new_character | storage_slots: new_slots}
send_storage_expanded(client_state, new_slots)
else
{:error, code} -> send_error(code, client_state)
end
end
end
@doc """
Expands character slots.
"""
@spec expand_character_slots(integer(), integer(), map()) :: map()
def expand_character_slots(sn, to_charge, client_state) do
character = client_state.character
with {:ok, item} <- validate_cash_item(sn),
:ok <- check_cash_balance(character, to_charge, item.price),
true <- item.item_id == 5_430_000,
current_slots <- client_state.account.character_slots || 3,
true <- current_slots < 15 do
# Deduct NX
new_character = modify_cs_points(character, to_charge, -item.price)
# Add slot
Context.increment_character_slots(client_state.account.id)
client_state
|> Map.put(:character, new_character)
|> send_character_slots_expanded(current_slots + 1)
else
_ -> send_error(@error_none, client_state)
end
end
@doc """
Moves item from cash inventory to regular inventory.
"""
@spec move_to_inventory(integer(), map()) :: map()
def move_to_inventory(cash_id, client_state) do
character = client_state.character
with {:ok, item} <- find_cash_item(character, cash_id),
:ok <- check_inventory_space(character, item.item_id, item.quantity),
{:ok, new_character, position} <- add_to_inventory(character, item) do
# Remove from cash inventory
new_character = remove_from_cash_inventory(new_character, cash_id)
client_state
|> Map.put(:character, new_character)
|> send_moved_to_inventory(item, position)
else
{:error, code} -> send_error(code, client_state)
end
end
@doc """
Moves item from regular inventory to cash inventory.
"""
@spec move_to_cash_inventory(integer(), integer(), map()) :: map()
def move_to_cash_inventory(unique_id, inv_type, client_state) do
character = client_state.character
with {:ok, item} <- find_inventory_item(character, inv_type, unique_id),
:ok <- check_cash_inventory_space(character) do
# Remove from inventory
new_character = remove_from_inventory(character, inv_type, unique_id)
# Add to cash inventory
cash_item = %{item | position: 0}
new_character = add_to_cash_inventory(new_character, cash_item)
send_moved_to_cash_inventory(client_state, cash_item)
else
{:error, code} -> send_error(code, client_state)
end
end
@doc """
Buys a friendship/crush ring.
"""
@spec buy_ring(integer(), String.t(), String.t(), integer(), map()) :: map()
def buy_ring(sn, partner_name, msg, to_charge, client_state) do
character = client_state.character
with {:ok, item} <- validate_cash_item(sn),
:ok <- validate_gift_message(msg),
:ok <- check_gender(item, character),
:ok <- check_cash_inventory_space(character),
:ok <- check_cash_balance(character, to_charge, item.price),
{:ok, target} <- find_character_by_name(partner_name),
:ok <- validate_ring_target(character, target) do
# Create ring (simplified - would need proper ring creation)
# Deduct NX
new_character = modify_cs_points(character, to_charge, -item.price)
client_state
|> Map.put(:character, new_character)
|> send_ring_purchased(item, partner_name)
else
{:error, code} -> send_error(code, client_state)
end
end
@doc """
Buys a package (contains multiple items).
"""
@spec buy_package(integer(), integer(), map()) :: map()
def buy_package(sn, to_charge, client_state) do
character = client_state.character
with {:ok, item} <- validate_cash_item(sn),
{:ok, package_items} <- get_package_items(item.item_id),
:ok <- check_gender(item, character),
:ok <- check_cash_inventory_space_for_package(character, length(package_items)),
:ok <- check_cash_balance(character, to_charge, item.price) do
# Deduct NX
new_character = modify_cs_points(character, to_charge, -item.price)
# Add all package items to inventory
{new_character, items_added} =
Enum.reduce(package_items, {new_character, []}, fn pkg_sn, {char, list} ->
case CashItemFactory.get_simple_item(pkg_sn) do
nil ->
{char, list}
pkg_item ->
cash_item = create_cash_item(pkg_item, "")
char = add_to_cash_inventory(char, cash_item)
{char, [cash_item | list]}
end
end)
client_state
|> Map.put(:character, new_character)
|> send_package_purchased(items_added)
else
{:error, code} -> send_error(code, client_state)
end
end
@doc """
Buys a quest item with meso.
"""
@spec buy_quest_item(integer(), map()) :: map()
def buy_quest_item(sn, client_state) do
character = client_state.character
with {:ok, item} <- validate_cash_item(sn),
true <- ItemInfo.is_quest?(item.item_id),
:ok <- check_meso_balance(character, item.price),
:ok <- check_inventory_space(character, item.item_id, item.count) do
# Deduct meso
new_character = %{character | meso: character.meso - item.price}
# Add item
{:ok, new_character, position} = add_item_to_inventory(new_character, item)
client_state
|> Map.put(:character, new_character)
|> send_quest_item_purchased(item, position)
else
_ -> send_error(@error_none, client_state)
end
end
# ==============================================================================
# Helper Functions
# ==============================================================================
defp validate_cash_item(sn) do
case CashItemFactory.get_item(sn) do
nil -> {:error, @error_none}
item -> {:ok, item}
end
end
defp check_gender(item, character) do
if CashItem.gender_matches?(item, character.gender) do
:ok
else
{:error, @error_invalid_gender}
end
end
defp check_cash_inventory_space(character) do
cash_items = character.cash_inventory || []
if length(cash_items) >= 100 do
{:error, @error_no_space}
else
:ok
end
end
defp check_cash_inventory_space_for_package(character, count) do
cash_items = character.cash_inventory || []
if length(cash_items) + count > 100 do
{:error, @error_no_space}
else
:ok
end
end
defp check_blocked_item(item) do
if CashItemFactory.blocked?(item.item_id) do
{:error, @error_none}
else
:ok
end
end
defp check_cash_balance(character, type, amount) do
balance = if type == 1, do: character.nx_cash || 0, else: character.maple_points || 0
if balance >= amount do
:ok
else
{:error, @error_none}
end
end
defp check_meso_balance(character, amount) do
if character.meso >= amount do
:ok
else
{:error, @error_not_enough_meso}
end
end
@doc """
Checks if there's space in inventory for an item.
"""
@spec check_inventory_space(map(), integer(), integer()) :: :ok | {:error, integer()}
def check_inventory_space(character, item_id, quantity) do
inv_type = InventoryType.from_item_id(item_id)
if Inventory.has_space?(character.inventories[inv_type], item_id, quantity) do
:ok
else
{:error, @error_no_space}
end
end
defp check_slot_limit(character, inventory_type) do
slots = get_current_slots(character, inventory_type)
if slots >= 96 do
{:error, @error_invalid_slot}
else
:ok
end
end
defp validate_gift_message(msg) do
if String.length(msg) > 73 || msg == "" do
{:error, @error_none}
else
:ok
end
end
defp find_character_by_name(name) do
case Context.get_character_by_name(name) do
nil -> {:error, @error_invalid_target}
character -> {:ok, character}
end
end
defp validate_gift_target(character, target) do
cond do
target.id == character.id -> {:error, @error_invalid_target}
target.account_id == character.account_id -> {:error, @error_same_account}
true -> :ok
end
end
defp validate_ring_target(character, target) do
cond do
target.id == character.id -> {:error, @error_invalid_ring}
target.account_id == character.account_id -> {:error, @error_same_account}
true -> :ok
end
end
defp get_package_items(item_id) do
case CashItemFactory.get_package_items(item_id) do
nil -> {:error, @error_none}
items -> {:ok, items}
end
end
defp create_gift(recipient_id, from, msg, item) do
Context.create_gift(%{
recipient_id: recipient_id,
from: from,
message: msg,
sn: item.sn,
unique_id: generate_unique_id()
})
end
defp create_cash_item(cash_item_info, gift_from) do
%{
unique_id: generate_unique_id(),
item_id: cash_item_info.item_id,
quantity: cash_item_info.count,
expiration: CashItem.expiration_time(cash_item_info),
gift_from: gift_from,
sn: cash_item_info.sn
}
end
defp modify_cs_points(character, type, amount) do
if type == 1 do
%{character | nx_cash: (character.nx_cash || 0) + amount}
else
%{character | maple_points: (character.maple_points || 0) + amount}
end
end
defp add_to_cash_inventory(character, item) do
cash_inv = character.cash_inventory || []
%{character | cash_inventory: [item | cash_inv]}
end
defp remove_from_cash_inventory(character, cash_id) do
cash_inv =
Enum.reject(character.cash_inventory || [], fn item ->
item.unique_id == cash_id
end)
%{character | cash_inventory: cash_inv}
end
defp find_cash_item(character, cash_id) do
case Enum.find(character.cash_inventory || [], &(&1.unique_id == cash_id)) do
nil -> {:error, @error_none}
item -> {:ok, item}
end
end
defp get_inventory_type(inv_type) do
case inv_type do
1 -> {:ok, :equip}
2 -> {:ok, :use}
3 -> {:ok, :setup}
4 -> {:ok, :etc}
_ -> {:error, @error_invalid_slot}
end
end
defp get_inventory_type_from_item(item) do
type = div(item.item_id, 1000)
case type do
9111 -> {:ok, :equip}
9112 -> {:ok, :use}
9113 -> {:ok, :setup}
9114 -> {:ok, :etc}
_ -> {:error, @error_invalid_slot}
end
end
defp get_current_slots(character, inventory_type) do
case character.inventory_limits[inventory_type] do
nil -> 24
limit -> limit
end
end
defp add_inventory_slots(character, inventory_type, slots) do
current = get_current_slots(character, inventory_type)
new_limits = Map.put(character.inventory_limits || %{}, inventory_type, current + slots)
%{character | inventory_limits: new_limits}
end
defp find_inventory_item(character, inv_type, unique_id) do
inventory = Map.get(character.inventories, inv_type, [])
case Enum.find(inventory, &(&1.unique_id == unique_id)) do
nil -> {:error, @error_none}
item -> {:ok, item}
end
end
defp remove_from_inventory(character, inv_type, unique_id) do
inventory = Map.get(character.inventories, inv_type, [])
new_inventory = Enum.reject(inventory, &(&1.unique_id == unique_id))
inventories = Map.put(character.inventories, inv_type, new_inventory)
%{character | inventories: inventories}
end
defp add_to_inventory(character, item) do
inv_type = InventoryType.from_item_id(item.item_id)
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}
end
defp add_item_to_inventory(character, item) do
inv_type = InventoryType.from_item_id(item.item_id)
inventory = Map.get(character.inventories, inv_type, [])
position = Inventory.next_free_slot(inventory)
new_item = %{
unique_id: generate_unique_id(),
item_id: item.item_id,
position: position,
quantity: item.count
}
new_inventory = [new_item | inventory]
inventories = Map.put(character.inventories, inv_type, new_inventory)
{:ok, %{character | inventories: inventories}, position}
end
defp apply_coupon_reward(type, value, client_state) do
character = client_state.character
{new_character, items, maple_points, mesos} =
case type do
1 ->
# NX Cash
{modify_cs_points(character, 1, value), %{}, value, 0}
2 ->
# Maple Points
{modify_cs_points(character, 2, value), %{}, value, 0}
3 ->
# Item
case CashItemFactory.get_item(value) do
nil ->
{character, %{}, 0, 0}
item ->
cash_item = create_cash_item(item, "")
new_char = add_to_cash_inventory(character, cash_item)
{new_char, %{value => cash_item}, 0, 0}
end
4 ->
# Mesos
{%{character | meso: character.meso + value}, %{}, 0, value}
_ ->
{character, %{}, 0, 0}
end
client_state
|> Map.put(:character, new_character)
|> send_coupon_redeemed(items, maple_points, mesos)
end
defp pad_wishlist(list) do
padding = List.duplicate(0, 10 - length(list))
list ++ padding
end
defp generate_unique_id do
:erlang.unique_integer([:positive])
end
# ==============================================================================
# Packet Senders (delegated to Packets module)
# ==============================================================================
defp send_error(code, client_state) do
Odinsea.Shop.Packets.send_cs_fail(client_state.socket, code)
client_state
end
defp send_bought_item(client_state, item, sn) do
Odinsea.Shop.Packets.show_bought_cs_item(client_state.socket, item, sn, client_state.account_id)
client_state
end
defp send_gift_sent(client_state, item, partner) do
Odinsea.Shop.Packets.send_gift(client_state.socket, item.price, item.item_id, item.count, partner)
client_state
end
defp send_wishlist(client_state, wishlist) do
Odinsea.Shop.Packets.send_wishlist(client_state.socket, client_state.character, wishlist)
client_state
end
defp send_inventory_expanded(client_state, inv_type, slots) do
# Send appropriate packet
Odinsea.Shop.Packets.enable_cs_use(client_state.socket)
client_state
end
defp send_storage_expanded(client_state, slots) do
# Send appropriate packet
Odinsea.Shop.Packets.enable_cs_use(client_state.socket)
client_state
end
defp send_character_slots_expanded(client_state, slots) do
Odinsea.Shop.Packets.enable_cs_use(client_state.socket)
client_state
end
defp send_moved_to_inventory(client_state, item, position) do
Odinsea.Shop.Packets.confirm_from_cs_inventory(client_state.socket, item, position)
client_state
end
defp send_moved_to_cash_inventory(client_state, item) do
Odinsea.Shop.Packets.confirm_to_cs_inventory(client_state.socket, item, client_state.account_id)
client_state
end
defp send_ring_purchased(client_state, item, partner) do
Odinsea.Shop.Packets.send_gift(client_state.socket, item.price, item.item_id, item.count, partner)
client_state
end
defp send_package_purchased(client_state, items) do
Odinsea.Shop.Packets.show_bought_cs_package(client_state.socket, items, client_state.account_id)
client_state
end
defp send_quest_item_purchased(client_state, item, position) do
Odinsea.Shop.Packets.show_bought_cs_quest_item(client_state.socket, item, position)
client_state
end
defp send_coupon_redeemed(client_state, items, maple_points, mesos) do
Odinsea.Shop.Packets.show_coupon_redeemed(client_state.socket, items, maple_points, mesos, client_state)
client_state
end
defp send_redeem_response(client_state) do
Odinsea.Shop.Packets.redeem_response(client_state.socket)
client_state
end
end