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) """ @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 } 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 ] # 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) 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 } 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 } 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. """ 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 true end end @doc """ Checks if this is a meso drop. """ def meso?(%__MODULE__{} = drop) do drop.meso > 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. """ 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 end