187 lines
5.7 KiB
Elixir
187 lines
5.7 KiB
Elixir
defmodule Odinsea.Shop.CashItem do
|
|
@moduledoc """
|
|
Cash Shop Item struct and utilities.
|
|
|
|
Represents an item available for purchase in the Cash Shop.
|
|
Ported from server/CashItemInfo.java and server/cash/CashCommodity.java
|
|
"""
|
|
|
|
@type t :: %__MODULE__{
|
|
sn: integer(),
|
|
item_id: integer(),
|
|
price: integer(),
|
|
count: integer(),
|
|
period: integer(),
|
|
gender: integer(),
|
|
on_sale: boolean(),
|
|
class: integer(),
|
|
priority: integer(),
|
|
is_package: boolean(),
|
|
meso_price: integer(),
|
|
bonus: integer(),
|
|
for_premium_user: integer(),
|
|
limit: integer(),
|
|
extra_flags: integer()
|
|
}
|
|
|
|
defstruct [
|
|
:sn,
|
|
:item_id,
|
|
:price,
|
|
:count,
|
|
:period,
|
|
:gender,
|
|
:on_sale,
|
|
:class,
|
|
:priority,
|
|
:is_package,
|
|
:meso_price,
|
|
:bonus,
|
|
:for_premium_user,
|
|
:limit,
|
|
:extra_flags
|
|
]
|
|
|
|
@doc """
|
|
Creates a new CashItem struct from parsed data.
|
|
"""
|
|
@spec new(map()) :: t()
|
|
def new(attrs) do
|
|
%__MODULE__{
|
|
sn: Map.get(attrs, :sn, 0),
|
|
item_id: Map.get(attrs, :item_id, 0),
|
|
price: Map.get(attrs, :price, 0),
|
|
count: Map.get(attrs, :count, 1),
|
|
period: Map.get(attrs, :period, 0),
|
|
gender: Map.get(attrs, :gender, 2),
|
|
on_sale: Map.get(attrs, :on_sale, false),
|
|
class: Map.get(attrs, :class, 0),
|
|
priority: Map.get(attrs, :priority, 0),
|
|
is_package: Map.get(attrs, :is_package, false),
|
|
meso_price: Map.get(attrs, :meso_price, 0),
|
|
bonus: Map.get(attrs, :bonus, 0),
|
|
for_premium_user: Map.get(attrs, :for_premium_user, 0),
|
|
limit: Map.get(attrs, :limit, 0),
|
|
extra_flags: Map.get(attrs, :extra_flags, 0)
|
|
}
|
|
end
|
|
|
|
@doc """
|
|
Checks if the item gender matches the player's gender.
|
|
Gender: 0 = male, 1 = female, 2 = both
|
|
"""
|
|
@spec gender_matches?(t(), integer()) :: boolean()
|
|
def gender_matches?(%__MODULE__{gender: 2}, _player_gender), do: true
|
|
def gender_matches?(%__MODULE__{gender: gender}, player_gender), do: gender == player_gender
|
|
|
|
@doc """
|
|
Calculates the flags value for packet encoding.
|
|
This follows the Java CashCommodity flag calculation.
|
|
"""
|
|
@spec calculate_flags(t()) :: integer()
|
|
def calculate_flags(item) do
|
|
flags = item.extra_flags || 0
|
|
|
|
flags = if item.item_id > 0, do: Bitwise.bor(flags, 0x1), else: flags
|
|
flags = if item.count > 0, do: Bitwise.bor(flags, 0x2), else: flags
|
|
flags = if item.price > 0, do: Bitwise.bor(flags, 0x4), else: flags
|
|
flags = if item.bonus > 0, do: Bitwise.bor(flags, 0x8), else: flags
|
|
flags = if item.priority >= 0, do: Bitwise.bor(flags, 0x10), else: flags
|
|
flags = if item.period > 0, do: Bitwise.bor(flags, 0x20), else: flags
|
|
# 0x40 = nMaplePoint (not used)
|
|
flags = if item.meso_price > 0, do: Bitwise.bor(flags, 0x80), else: flags
|
|
flags = if item.for_premium_user > 0, do: Bitwise.bor(flags, 0x100), else: flags
|
|
flags = if item.gender >= 0, do: Bitwise.bor(flags, 0x200), else: flags
|
|
flags = if item.on_sale, do: Bitwise.bor(flags, 0x400), else: flags
|
|
flags = if item.class >= -1 && item.class <= 3, do: Bitwise.bor(flags, 0x800), else: flags
|
|
flags = if item.limit > 0, do: Bitwise.bor(flags, 0x1000), else: flags
|
|
# 0x2000, 0x4000, 0x8000 = nPbCash, nPbPoint, nPbGift (not used)
|
|
flags = if item.is_package, do: Bitwise.bor(flags, 0x40000), else: flags
|
|
# 0x80000, 0x100000 = term start/end (not used)
|
|
|
|
flags
|
|
end
|
|
|
|
@doc """
|
|
Checks if this is a cash item (premium currency item).
|
|
"""
|
|
@spec cash_item?(integer()) :: boolean()
|
|
def cash_item?(item_id) do
|
|
# Cash items typically have IDs in certain ranges
|
|
# This is a simplified check - full implementation would check WZ data
|
|
item_id >= 500_000 && item_id < 600_000
|
|
end
|
|
|
|
@doc """
|
|
Checks if this item is a pet.
|
|
"""
|
|
@spec pet?(t()) :: boolean()
|
|
def pet?(%__MODULE__{item_id: item_id}) do
|
|
item_id >= 5_000_000 && item_id < 5_100_000
|
|
end
|
|
|
|
@doc """
|
|
Checks if this is a permanent pet.
|
|
"""
|
|
@spec permanent_pet?(t()) :: boolean()
|
|
def permanent_pet?(%__MODULE__{item_id: item_id}) do
|
|
item_id >= 5_000_100 && item_id < 5_000_200
|
|
end
|
|
|
|
@doc """
|
|
Gets the effective period for this item.
|
|
Returns period in days, or special values for permanent items.
|
|
"""
|
|
@spec effective_period(t()) :: integer()
|
|
def effective_period(%__MODULE__{period: period} = item) do
|
|
cond do
|
|
# Permanent pets have special handling
|
|
permanent_pet?(item) -> 20_000
|
|
# Default period for non-equip cash items that aren't permanent
|
|
period <= 0 && !equip_item?(item) -> 90
|
|
true -> period
|
|
end
|
|
end
|
|
|
|
@doc """
|
|
Checks if this item is equipment.
|
|
"""
|
|
@spec equip_item?(t()) :: boolean()
|
|
def equip_item?(%__MODULE__{item_id: item_id}) do
|
|
item_id >= 1_000_000
|
|
end
|
|
|
|
@doc """
|
|
Calculates the expiration timestamp for this item.
|
|
"""
|
|
@spec expiration_time(t()) :: integer()
|
|
def expiration_time(item) do
|
|
period = effective_period(item)
|
|
|
|
if period > 0 do
|
|
Odinsea.now() + period * 24 * 60 * 60 * 1000
|
|
else
|
|
-1
|
|
end
|
|
end
|
|
|
|
@doc """
|
|
Applies modified item info (from cashshop_modified_items table).
|
|
"""
|
|
@spec apply_mods(t(), map()) :: t()
|
|
def apply_mods(item, mods) do
|
|
%__MODULE__{
|
|
item
|
|
| item_id: Map.get(mods, :item_id, item.item_id),
|
|
price: Map.get(mods, :price, item.price),
|
|
count: Map.get(mods, :count, item.count),
|
|
period: Map.get(mods, :period, item.period),
|
|
gender: Map.get(mods, :gender, item.gender),
|
|
on_sale: Map.get(mods, :on_sale, item.on_sale),
|
|
class: Map.get(mods, :class, item.class),
|
|
priority: Map.get(mods, :priority, item.priority),
|
|
is_package: Map.get(mods, :is_package, item.is_package)
|
|
}
|
|
end
|
|
end
|