Files
odinsea-elixir/lib/odinsea/game/drop.ex
2026-02-14 23:58:01 -07:00

262 lines
7.0 KiB
Elixir

defmodule Odinsea.Game.Drop do
@moduledoc """
Represents a drop item on a map.
Ported from Java server.maps.MapleMapItem
Drops can be:
- Item drops (equipment, use, setup, etc items)
- Meso drops (gold/money)
Drop ownership determines who can loot:
- Type 0: Timeout for non-owner only
- Type 1: Timeout for non-owner's party
- Type 2: Free-for-all (FFA)
- Type 3: Explosive/FFA (instant FFA)
"""
alias Odinsea.Game.Item
@type t :: %__MODULE__{
oid: integer(),
item_id: integer(),
quantity: integer(),
meso: integer(),
owner_id: integer(),
drop_type: integer(),
position: %{x: integer(), y: integer()},
source_position: %{x: integer(), y: integer()} | nil,
quest_id: integer(),
player_drop: boolean(),
individual_reward: boolean(),
picked_up: boolean(),
created_at: integer(),
expire_time: integer() | nil,
public_time: integer() | nil,
dropper_oid: integer() | nil,
# Item struct for item drops (nil for meso drops)
item: Item.t() | nil
}
defstruct [
:oid,
:item_id,
:quantity,
:meso,
:owner_id,
:drop_type,
:position,
:source_position,
:quest_id,
:player_drop,
:individual_reward,
:picked_up,
:created_at,
:expire_time,
:public_time,
:dropper_oid,
:item
]
# Default drop expiration times (milliseconds)
@default_expire_time 120_000 # 2 minutes
@default_public_time 60_000 # 1 minute until FFA
@doc """
Creates a new item drop.
"""
def new_item_drop(oid, item_id, quantity, owner_id, position, opts \\ []) do
drop_type = Keyword.get(opts, :drop_type, 0)
quest_id = Keyword.get(opts, :quest_id, -1)
individual_reward = Keyword.get(opts, :individual_reward, false)
dropper_oid = Keyword.get(opts, :dropper_oid, nil)
source_position = Keyword.get(opts, :source_position, nil)
item = Keyword.get(opts, :item, nil)
now = System.system_time(:millisecond)
%__MODULE__{
oid: oid,
item_id: item_id,
quantity: quantity,
meso: 0,
owner_id: owner_id,
drop_type: drop_type,
position: position,
source_position: source_position,
quest_id: quest_id,
player_drop: false,
individual_reward: individual_reward,
picked_up: false,
created_at: now,
expire_time: now + @default_expire_time,
public_time: if(drop_type < 2, do: now + @default_public_time, else: 0),
dropper_oid: dropper_oid,
item: item
}
end
@doc """
Creates a new meso drop.
"""
def new_meso_drop(oid, amount, owner_id, position, opts \\ []) do
drop_type = Keyword.get(opts, :drop_type, 0)
individual_reward = Keyword.get(opts, :individual_reward, false)
dropper_oid = Keyword.get(opts, :dropper_oid, nil)
source_position = Keyword.get(opts, :source_position, nil)
now = System.system_time(:millisecond)
%__MODULE__{
oid: oid,
item_id: 0,
quantity: 0,
meso: amount,
owner_id: owner_id,
drop_type: drop_type,
position: position,
source_position: source_position,
quest_id: -1,
player_drop: false,
individual_reward: individual_reward,
picked_up: false,
created_at: now,
expire_time: now + @default_expire_time,
public_time: if(drop_type < 2, do: now + @default_public_time, else: 0),
dropper_oid: dropper_oid,
item: nil
}
end
@doc """
Marks the drop as picked up.
"""
def mark_picked_up(%__MODULE__{} = drop) do
%{drop | picked_up: true}
end
@doc """
Checks if the drop should expire based on current time.
"""
def should_expire?(%__MODULE__{} = drop, now) do
not drop.picked_up and drop.expire_time != nil and drop.expire_time < now
end
@doc """
Checks if the drop has become public (FFA) based on current time.
"""
def is_public_time?(%__MODULE__{} = drop, now) do
not drop.picked_up and drop.public_time != nil and drop.public_time < now
end
@doc """
Checks if a drop is visible to a specific character.
Considers quest requirements and individual rewards.
For quest items, the character must have the quest started.
For individual rewards, only the owner can see the drop.
"""
def visible_to?(%__MODULE__{} = drop, character_id, quest_status) do
# Individual rewards only visible to owner
if drop.individual_reward do
drop.owner_id == character_id
else
# Check quest requirement
if drop.quest_id > 0 do
# Only visible if character has quest started (status 1)
Map.get(quest_status, drop.quest_id, 0) == 1
else
true
end
end
end
@doc """
Checks if this is a meso drop.
"""
def meso?(%__MODULE__{} = drop) do
drop.meso > 0
end
@doc """
Checks if this is an item drop.
"""
def item?(%__MODULE__{} = drop) do
drop.meso == 0 and drop.item_id > 0
end
@doc """
Gets the display ID (item_id for items, meso amount for meso).
"""
def display_id(%__MODULE__{} = drop) do
if drop.meso > 0 do
drop.meso
else
drop.item_id
end
end
@doc """
Checks if a character can loot this drop based on ownership rules.
Drop types:
- 0: Owner only (until timeout)
- 1: Owner's party (until timeout)
- 2: Free-for-all (FFA)
- 3: Explosive (instant FFA)
"""
def can_loot?(%__MODULE__{} = drop, character_id, now) do
# If already picked up, can't loot
if drop.picked_up do
false
else
# Check ownership rules based on drop type
case drop.drop_type do
0 ->
# Timeout for non-owner only
drop.owner_id == character_id or is_public_time?(drop, now)
1 ->
# Timeout for non-owner's party (simplified - treat as FFA after timeout)
drop.owner_id == character_id or is_public_time?(drop, now)
2 ->
# FFA
true
3 ->
# Explosive/FFA (instant FFA)
true
_ ->
# Default to owner-only
drop.owner_id == character_id
end
end
end
@doc """
Checks if a character can loot this drop, including party check.
Requires party information to validate party drops.
"""
def can_loot_with_party?(%__MODULE__{} = drop, character_id, party_id, party_members, now) do
if drop.picked_up do
false
else
case drop.drop_type do
0 ->
# Timeout for non-owner only
drop.owner_id == character_id or is_public_time?(drop, now)
1 ->
# Timeout for non-owner's party
owner_in_same_party = drop.owner_id in party_members
(owner_in_same_party and party_id != nil) or
drop.owner_id == character_id or
is_public_time?(drop, now)
2 ->
# FFA
true
3 ->
# Explosive/FFA (instant FFA)
true
_ ->
# Default to owner-only
drop.owner_id == character_id
end
end
end
end