update
This commit is contained in:
254
lib/odinsea/game/monster.ex
Normal file
254
lib/odinsea/game/monster.ex
Normal file
@@ -0,0 +1,254 @@
|
||||
defmodule Odinsea.Game.Monster do
|
||||
@moduledoc """
|
||||
Represents a monster (mob) instance on a map.
|
||||
|
||||
Monsters are managed by the Map GenServer, not as separate processes.
|
||||
Each monster has stats, position, HP tracking, and controller assignment.
|
||||
"""
|
||||
|
||||
alias Odinsea.Game.LifeFactory
|
||||
|
||||
@type t :: %__MODULE__{
|
||||
oid: integer(),
|
||||
mob_id: integer(),
|
||||
stats: LifeFactory.MonsterStats.t(),
|
||||
hp: integer(),
|
||||
mp: integer(),
|
||||
max_hp: integer(),
|
||||
max_mp: integer(),
|
||||
position: %{x: integer(), y: integer(), fh: integer()},
|
||||
stance: integer(),
|
||||
controller_id: integer() | nil,
|
||||
controller_has_aggro: boolean(),
|
||||
spawn_effect: integer(),
|
||||
team: integer(),
|
||||
fake: boolean(),
|
||||
link_oid: integer(),
|
||||
status_effects: %{atom() => any()},
|
||||
poisons: [any()],
|
||||
attackers: %{integer() => %{damage: integer(), last_hit: integer()}},
|
||||
last_attack: integer(),
|
||||
last_move: integer(),
|
||||
last_skill_use: integer(),
|
||||
killed: boolean(),
|
||||
drops_disabled: boolean(),
|
||||
create_time: integer()
|
||||
}
|
||||
|
||||
defstruct [
|
||||
:oid,
|
||||
:mob_id,
|
||||
:stats,
|
||||
:hp,
|
||||
:mp,
|
||||
:max_hp,
|
||||
:max_mp,
|
||||
:position,
|
||||
:stance,
|
||||
:controller_id,
|
||||
:controller_has_aggro,
|
||||
:spawn_effect,
|
||||
:team,
|
||||
:fake,
|
||||
:link_oid,
|
||||
:status_effects,
|
||||
:poisons,
|
||||
:attackers,
|
||||
:last_attack,
|
||||
:last_move,
|
||||
:last_skill_use,
|
||||
:killed,
|
||||
:drops_disabled,
|
||||
:create_time
|
||||
]
|
||||
|
||||
@doc """
|
||||
Creates a new monster instance.
|
||||
"""
|
||||
def new(mob_id, oid, position) do
|
||||
stats = LifeFactory.get_monster_stats(mob_id)
|
||||
|
||||
if stats do
|
||||
%__MODULE__{
|
||||
oid: oid,
|
||||
mob_id: mob_id,
|
||||
stats: stats,
|
||||
hp: stats.hp,
|
||||
mp: stats.mp,
|
||||
max_hp: stats.hp,
|
||||
max_mp: stats.mp,
|
||||
position: position,
|
||||
stance: 5,
|
||||
controller_id: nil,
|
||||
controller_has_aggro: false,
|
||||
spawn_effect: 0,
|
||||
team: -1,
|
||||
fake: false,
|
||||
link_oid: 0,
|
||||
status_effects: %{},
|
||||
poisons: [],
|
||||
attackers: %{},
|
||||
last_attack: System.system_time(:millisecond),
|
||||
last_move: System.system_time(:millisecond),
|
||||
last_skill_use: 0,
|
||||
killed: false,
|
||||
drops_disabled: false,
|
||||
create_time: System.system_time(:millisecond)
|
||||
}
|
||||
else
|
||||
nil
|
||||
end
|
||||
end
|
||||
|
||||
@doc """
|
||||
Damages the monster.
|
||||
Returns {:ok, monster, damage_dealt} or {:dead, monster, damage_dealt}
|
||||
"""
|
||||
def damage(%__MODULE__{} = monster, damage_amount, attacker_id) do
|
||||
# Track attacker
|
||||
attacker_entry = Map.get(monster.attackers, attacker_id, %{damage: 0, last_hit: 0})
|
||||
|
||||
new_attacker_entry = %{
|
||||
attacker_entry
|
||||
| damage: attacker_entry.damage + damage_amount,
|
||||
last_hit: System.system_time(:millisecond)
|
||||
}
|
||||
|
||||
new_attackers = Map.put(monster.attackers, attacker_id, new_attacker_entry)
|
||||
|
||||
# Apply damage
|
||||
new_hp = max(0, monster.hp - damage_amount)
|
||||
new_monster = %{monster | hp: new_hp, attackers: new_attackers}
|
||||
|
||||
if new_hp <= 0 do
|
||||
{:dead, %{new_monster | killed: true}, damage_amount}
|
||||
else
|
||||
{:ok, new_monster, damage_amount}
|
||||
end
|
||||
end
|
||||
|
||||
@doc """
|
||||
Heals the monster.
|
||||
"""
|
||||
def heal(%__MODULE__{} = monster, heal_amount) do
|
||||
new_hp = min(monster.max_hp, monster.hp + heal_amount)
|
||||
%{monster | hp: new_hp}
|
||||
end
|
||||
|
||||
@doc """
|
||||
Sets the monster's controller.
|
||||
"""
|
||||
def set_controller(%__MODULE__{} = monster, controller_id) do
|
||||
%{monster | controller_id: controller_id, controller_has_aggro: true}
|
||||
end
|
||||
|
||||
@doc """
|
||||
Removes the monster's controller.
|
||||
"""
|
||||
def clear_controller(%__MODULE__{} = monster) do
|
||||
%{monster | controller_id: nil, controller_has_aggro: false}
|
||||
end
|
||||
|
||||
@doc """
|
||||
Updates the monster's position.
|
||||
"""
|
||||
def update_position(%__MODULE__{} = monster, position) do
|
||||
%{monster | position: position, last_move: System.system_time(:millisecond)}
|
||||
end
|
||||
|
||||
@doc """
|
||||
Checks if the monster is boss.
|
||||
"""
|
||||
def boss?(%__MODULE__{} = monster) do
|
||||
monster.stats.boss
|
||||
end
|
||||
|
||||
@doc """
|
||||
Checks if the monster is dead.
|
||||
"""
|
||||
def dead?(%__MODULE__{} = monster) do
|
||||
monster.hp <= 0 || monster.killed
|
||||
end
|
||||
|
||||
@doc """
|
||||
Gets the monster's name.
|
||||
"""
|
||||
def name(%__MODULE__{} = monster) do
|
||||
monster.stats.name
|
||||
end
|
||||
|
||||
@doc """
|
||||
Gets the monster's level.
|
||||
"""
|
||||
def level(%__MODULE__{} = monster) do
|
||||
monster.stats.level
|
||||
end
|
||||
|
||||
@doc """
|
||||
Calculates EXP drop for this monster.
|
||||
"""
|
||||
def calculate_exp(%__MODULE__{} = monster, exp_rate \\ 1.0) do
|
||||
base_exp = monster.stats.exp
|
||||
trunc(base_exp * exp_rate)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Gets the top attacker (highest damage dealer).
|
||||
"""
|
||||
def get_top_attacker(%__MODULE__{} = monster) do
|
||||
if Enum.empty?(monster.attackers) do
|
||||
nil
|
||||
else
|
||||
{attacker_id, _entry} =
|
||||
Enum.max_by(monster.attackers, fn {_id, entry} -> entry.damage end)
|
||||
|
||||
attacker_id
|
||||
end
|
||||
end
|
||||
|
||||
@doc """
|
||||
Gets all attackers sorted by damage (descending).
|
||||
"""
|
||||
def get_attackers_sorted(%__MODULE__{} = monster) do
|
||||
monster.attackers
|
||||
|> Enum.sort_by(fn {_id, entry} -> entry.damage end, :desc)
|
||||
|> Enum.map(fn {id, entry} -> {id, entry.damage} end)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Applies a status effect to the monster.
|
||||
"""
|
||||
def apply_status_effect(%__MODULE__{} = monster, effect_name, effect_data) do
|
||||
new_effects = Map.put(monster.status_effects, effect_name, effect_data)
|
||||
%{monster | status_effects: new_effects}
|
||||
end
|
||||
|
||||
@doc """
|
||||
Removes a status effect from the monster.
|
||||
"""
|
||||
def remove_status_effect(%__MODULE__{} = monster, effect_name) do
|
||||
new_effects = Map.delete(monster.status_effects, effect_name)
|
||||
%{monster | status_effects: new_effects}
|
||||
end
|
||||
|
||||
@doc """
|
||||
Checks if monster has a status effect.
|
||||
"""
|
||||
def has_status_effect?(%__MODULE__{} = monster, effect_name) do
|
||||
Map.has_key?(monster.status_effects, effect_name)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Disables drops for this monster.
|
||||
"""
|
||||
def disable_drops(%__MODULE__{} = monster) do
|
||||
%{monster | drops_disabled: true}
|
||||
end
|
||||
|
||||
@doc """
|
||||
Checks if drops are disabled.
|
||||
"""
|
||||
def drops_disabled?(%__MODULE__{} = monster) do
|
||||
monster.drops_disabled
|
||||
end
|
||||
end
|
||||
Reference in New Issue
Block a user