310 lines
7.3 KiB
Elixir
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
|