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