Files
odinsea-elixir/lib/odinsea/game/monster_status.ex
2026-02-14 23:12:33 -07:00

310 lines
7.3 KiB
Elixir

defmodule Odinsea.Game.MonsterStatus do
@moduledoc """
Monster status effects (buffs/debuffs) for MapleStory.
Ported from Java: client/status/MobStat.java
MonsterStatus represents status effects that can be applied to monsters:
- PAD (Physical Attack Damage)
- PDD (Physical Defense)
- MAD (Magic Attack Damage)
- MDD (Magic Defense)
- ACC (Accuracy)
- EVA (Evasion)
- Speed
- Stun
- Freeze
- Poison
- Seal
- And more...
"""
import Bitwise
@type t ::
:pad
| :pdd
| :mad
| :mdd
| :acc
| :eva
| :speed
| :stun
| :freeze
| :poison
| :seal
| :darkness
| :power_up
| :magic_up
| :p_guard_up
| :m_guard_up
| :doom
| :web
| :p_immune
| :m_immune
| :showdown
| :hard_skin
| :ambush
| :damaged_elem_attr
| :venom
| :blind
| :seal_skill
| :burned
| :dazzle
| :p_counter
| :m_counter
| :disable
| :rise_by_toss
| :body_pressure
| :weakness
| :time_bomb
| :magic_crash
| :exchange_attack
| :heal_by_damage
| :invincible
@doc """
All monster status effects with their bit values and positions.
Format: {status_name, bit_value, position}
Position 1 = first int, Position 2 = second int
"""
def all_statuses do
[
# Position 1 (first int)
{:pad, 0x1, 1},
{:pdd, 0x2, 1},
{:mad, 0x4, 1},
{:mdd, 0x8, 1},
{:acc, 0x10, 1},
{:eva, 0x20, 1},
{:speed, 0x40, 1},
{:stun, 0x80, 1},
{:freeze, 0x100, 1},
{:poison, 0x200, 1},
{:seal, 0x400, 1},
{:darkness, 0x800, 1},
{:power_up, 0x1000, 1},
{:magic_up, 0x2000, 1},
{:p_guard_up, 0x4000, 1},
{:m_guard_up, 0x8000, 1},
{:doom, 0x10000, 1},
{:web, 0x20000, 1},
{:p_immune, 0x40000, 1},
{:m_immune, 0x80000, 1},
{:showdown, 0x100000, 1},
{:hard_skin, 0x200000, 1},
{:ambush, 0x400000, 1},
{:damaged_elem_attr, 0x800000, 1},
{:venom, 0x1000000, 1},
{:blind, 0x2000000, 1},
{:seal_skill, 0x4000000, 1},
{:burned, 0x8000000, 1},
{:dazzle, 0x10000000, 1},
{:p_counter, 0x20000000, 1},
{:m_counter, 0x40000000, 1},
{:disable, 0x80000000, 1},
# Position 2 (second int)
{:rise_by_toss, 0x1, 2},
{:body_pressure, 0x2, 2},
{:weakness, 0x4, 2},
{:time_bomb, 0x8, 2},
{:magic_crash, 0x10, 2},
{:exchange_attack, 0x20, 2},
{:heal_by_damage, 0x40, 2},
{:invincible, 0x80, 2}
]
end
@doc """
Gets the bit value for a status effect.
"""
@spec get_bit(t()) :: integer()
def get_bit(status) do
case List.keyfind(all_statuses(), status, 0) do
{_, bit, _} -> bit
nil -> 0
end
end
@doc """
Gets the position (1 or 2) for a status effect.
"""
@spec get_position(t()) :: integer()
def get_position(status) do
case List.keyfind(all_statuses(), status, 0) do
{_, _, pos} -> pos
nil -> 1
end
end
@doc """
Checks if a status is in position 1.
"""
@spec position_1?(t()) :: boolean()
def position_1?(status) do
get_position(status) == 1
end
@doc """
Checks if a status is in position 2.
"""
@spec position_2?(t()) :: boolean()
def position_2?(status) do
get_position(status) == 2
end
@doc """
Gets all statuses in position 1.
"""
@spec position_1_statuses() :: [t()]
def position_1_statuses do
all_statuses()
|> Enum.filter(fn {_, _, pos} -> pos == 1 end)
|> Enum.map(fn {status, _, _} -> status end)
end
@doc """
Gets all statuses in position 2.
"""
@spec position_2_statuses() :: [t()]
def position_2_statuses do
all_statuses()
|> Enum.filter(fn {_, _, pos} -> pos == 2 end)
|> Enum.map(fn {status, _, _} -> status end)
end
@doc """
Encodes a map of status effects to bitmasks.
Returns {mask1, mask2} where each is an integer bitmask.
"""
@spec encode_statuses(%{t() => integer()}) :: {integer(), integer()}
def encode_statuses(statuses) when is_map(statuses) do
mask1 =
statuses
|> Enum.filter(fn {status, _} -> position_1?(status) end)
|> Enum.reduce(0, fn {status, _}, acc -> acc ||| get_bit(status) end)
mask2 =
statuses
|> Enum.filter(fn {status, _} -> position_2?(status) end)
|> Enum.reduce(0, fn {status, _}, acc -> acc ||| get_bit(status) end)
{mask1, mask2}
end
def encode_statuses(_), do: {0, 0}
@doc """
Decodes bitmasks to a list of status effects.
"""
@spec decode_statuses(integer(), integer()) :: [t()]
def decode_statuses(mask1, mask2) do
pos1 =
position_1_statuses()
|> Enum.filter(fn status -> (mask1 &&& get_bit(status)) != 0 end)
pos2 =
position_2_statuses()
|> Enum.filter(fn status -> (mask2 &&& get_bit(status)) != 0 end)
pos1 ++ pos2
end
@doc """
Gets the linked disease for a monster status.
Used when converting monster debuffs to player diseases.
"""
@spec get_linked_disease(t()) :: atom() | nil
def get_linked_disease(status) do
case status do
:stun -> :stun
:web -> :stun
:poison -> :poison
:venom -> :poison
:seal -> :seal
:magic_crash -> :seal
:freeze -> :freeze
:blind -> :darkness
:speed -> :slow
_ -> nil
end
end
@doc """
Gets the monster status from a Pokemon-style skill ID.
Used for familiar/capture card mechanics.
"""
@spec from_pokemon_skill(integer()) :: t() | nil
def from_pokemon_skill(skill_id) do
case skill_id do
120 -> :seal
121 -> :blind
123 -> :stun
125 -> :poison
126 -> :speed
137 -> :freeze
_ -> nil
end
end
@doc """
Checks if the status is a stun effect (prevents movement).
"""
@spec is_stun?(t()) :: boolean()
def is_stun?(status) do
status in [:stun, :freeze]
end
@doc """
Checks if the status is a damage over time effect.
"""
@spec is_dot?(t()) :: boolean()
def is_dot?(status) do
status in [:poison, :venom, :burned]
end
@doc """
Checks if the status is a debuff (negative effect).
"""
@spec is_debuff?(t()) :: boolean()
def is_debuff?(status) do
status in [
:stun, :freeze, :poison, :seal, :darkness, :doom, :web,
:blind, :seal_skill, :burned, :dazzle, :speed, :weakness,
:time_bomb, :magic_crash, :disable, :rise_by_toss
]
end
@doc """
Checks if the status is a buff (positive effect).
"""
@spec is_buff?(t()) :: boolean()
def is_buff?(status) do
status in [
:pad, :pdd, :mad, :mdd, :acc, :eva, :power_up, :magic_up,
:p_guard_up, :m_guard_up, :hard_skin, :invincible, :heal_by_damage
]
end
@doc """
Gets the default duration for a status effect in milliseconds.
"""
@spec default_duration(t()) :: integer()
def default_duration(status) do
case status do
:stun -> 3000
:freeze -> 5000
:poison -> 8000
:seal -> 5000
:darkness -> 8000
:doom -> 10000
:web -> 8000
:blind -> 8000
:speed -> 8000
:weakness -> 8000
_ -> 5000
end
end
end