kimi gone wild
This commit is contained in:
787
lib/odinsea/scripting/event_instance.ex
Normal file
787
lib/odinsea/scripting/event_instance.ex
Normal file
@@ -0,0 +1,787 @@
|
||||
defmodule Odinsea.Scripting.EventInstance do
|
||||
@moduledoc """
|
||||
Event Instance Manager for individual event/quest instances.
|
||||
|
||||
Each event instance represents a running copy of a party quest or event,
|
||||
with its own state, players, maps, and timers.
|
||||
|
||||
## State
|
||||
|
||||
Event instances track:
|
||||
- Players registered to the event
|
||||
- Monsters spawned
|
||||
- Kill counts per player
|
||||
- Map instances (cloned maps for PQ)
|
||||
- Custom properties
|
||||
- Event timer
|
||||
|
||||
## Lifecycle
|
||||
|
||||
1. EventManager creates instance via `new/3`
|
||||
2. Script `setup/2` is called
|
||||
3. Players register via `register_player/2`
|
||||
4. Event callbacks fire as things happen
|
||||
5. Instance disposes when complete or empty
|
||||
"""
|
||||
|
||||
require Logger
|
||||
|
||||
alias Odinsea.Game.Character
|
||||
|
||||
# ============================================================================
|
||||
# Types
|
||||
# ============================================================================
|
||||
|
||||
@type t :: %__MODULE__{
|
||||
event_manager: Odinsea.Scripting.EventManager.t() | nil,
|
||||
name: String.t(),
|
||||
channel: integer(),
|
||||
players: [integer()],
|
||||
disconnected: [integer()],
|
||||
monsters: [integer()],
|
||||
kill_count: %{integer() => integer()},
|
||||
map_ids: [integer()],
|
||||
is_instanced: [boolean()],
|
||||
properties: %{String.t() => String.t()},
|
||||
timer_started: boolean(),
|
||||
time_started: integer() | nil,
|
||||
event_time: integer() | nil,
|
||||
disposed: boolean()
|
||||
}
|
||||
|
||||
defstruct [
|
||||
:event_manager,
|
||||
:name,
|
||||
:channel,
|
||||
players: [],
|
||||
disconnected: [],
|
||||
monsters: [],
|
||||
kill_count: %{},
|
||||
map_ids: [],
|
||||
is_instanced: [],
|
||||
properties: %{},
|
||||
timer_started: false,
|
||||
time_started: nil,
|
||||
event_time: nil,
|
||||
disposed: false
|
||||
]
|
||||
|
||||
# ============================================================================
|
||||
# Constructor
|
||||
# ============================================================================
|
||||
|
||||
@doc """
|
||||
Creates a new event instance.
|
||||
|
||||
## Parameters
|
||||
- `event_manager` - Parent EventManager
|
||||
- `name` - Unique instance name
|
||||
- `channel` - Channel number
|
||||
"""
|
||||
@spec new(Odinsea.Scripting.EventManager.t() | nil, String.t(), integer()) :: t()
|
||||
def new(event_manager, name, channel) do
|
||||
%__MODULE__{
|
||||
event_manager: event_manager,
|
||||
name: name,
|
||||
channel: channel
|
||||
}
|
||||
end
|
||||
|
||||
# ============================================================================
|
||||
# Player Management
|
||||
# ============================================================================
|
||||
|
||||
@doc """
|
||||
Registers a player to this event instance.
|
||||
|
||||
## Parameters
|
||||
- `eim` - Event instance
|
||||
- `player` - Character struct or ID
|
||||
"""
|
||||
@spec register_player(t(), Character.t() | integer()) :: t()
|
||||
def register_player(%{disposed: true} = eim, _), do: eim
|
||||
def register_player(eim, player) do
|
||||
char_id = case player do
|
||||
%{id: id} -> id
|
||||
id when is_integer(id) -> id
|
||||
end
|
||||
|
||||
# Add to player list
|
||||
players = [char_id | eim.players] |> Enum.uniq()
|
||||
|
||||
%{eim | players: players}
|
||||
|> call_callback(:player_entry, [player])
|
||||
end
|
||||
|
||||
@doc """
|
||||
Unregisters a player from this event instance.
|
||||
"""
|
||||
@spec unregister_player(t(), Character.t() | integer()) :: t()
|
||||
def unregister_player(%{disposed: true} = eim, _), do: eim
|
||||
def unregister_player(eim, player) do
|
||||
char_id = case player do
|
||||
%{id: id} -> id
|
||||
id when is_integer(id) -> id
|
||||
end
|
||||
|
||||
players = List.delete(eim.players, char_id)
|
||||
|
||||
%{eim | players: players}
|
||||
end
|
||||
|
||||
@doc """
|
||||
Handles player changing maps.
|
||||
"""
|
||||
@spec changed_map(t(), Character.t() | integer(), integer()) :: t()
|
||||
def changed_map(%{disposed: true} = eim, _, _), do: eim
|
||||
def changed_map(eim, player, map_id) do
|
||||
call_callback(eim, :changed_map, [player, map_id])
|
||||
end
|
||||
|
||||
@doc """
|
||||
Handles player death.
|
||||
"""
|
||||
@spec player_killed(t(), Character.t() | integer()) :: t()
|
||||
def player_killed(%{disposed: true} = eim, _), do: eim
|
||||
def player_killed(eim, player) do
|
||||
call_callback(eim, :player_dead, [player])
|
||||
end
|
||||
|
||||
@doc """
|
||||
Handles player revive request.
|
||||
|
||||
## Returns
|
||||
- `{allow_revive :: boolean(), updated_eim}`
|
||||
"""
|
||||
@spec revive_player(t(), Character.t() | integer()) :: {boolean(), t()}
|
||||
def revive_player(%{disposed: true} = eim, _), do: {true, eim}
|
||||
def revive_player(eim, player) do
|
||||
result = call_callback_result(eim, :player_revive, [player])
|
||||
allow = if is_boolean(result), do: result, else: true
|
||||
{allow, eim}
|
||||
end
|
||||
|
||||
@doc """
|
||||
Handles player disconnection.
|
||||
|
||||
## Returns
|
||||
- `{:dispose, eim}` - Dispose instance
|
||||
- `{:continue, eim}` - Continue running
|
||||
"""
|
||||
@spec player_disconnected(t(), Character.t() | integer()) :: {:dispose | :continue, t()}
|
||||
def player_disconnected(%{disposed: true} = eim, _), do: {:dispose, eim}
|
||||
def player_disconnected(eim, player) do
|
||||
char_id = case player do
|
||||
%{id: id} -> id
|
||||
id when is_integer(id) -> id
|
||||
end
|
||||
|
||||
# Add to disconnected list
|
||||
disconnected = [char_id | eim.disconnected]
|
||||
eim = %{eim | disconnected: disconnected}
|
||||
|
||||
# Remove from players
|
||||
eim = unregister_player(eim, player)
|
||||
|
||||
# Call callback to determine behavior
|
||||
result = call_callback_result(eim, :player_disconnected, [player])
|
||||
|
||||
action = case result do
|
||||
0 ->
|
||||
# Dispose if no players
|
||||
if length(eim.players) == 0, do: :dispose, else: :continue
|
||||
|
||||
x when x > 0 ->
|
||||
# Dispose if less than x players
|
||||
if length(eim.players) < x, do: :dispose, else: :continue
|
||||
|
||||
x when x < 0 ->
|
||||
# Dispose if less than |x| players, or if leader disconnected
|
||||
threshold = abs(x)
|
||||
if length(eim.players) < threshold do
|
||||
:dispose
|
||||
else
|
||||
# TODO: Check if leader disconnected
|
||||
:continue
|
||||
end
|
||||
|
||||
_ ->
|
||||
:continue
|
||||
end
|
||||
|
||||
{action, eim}
|
||||
end
|
||||
|
||||
@doc """
|
||||
Removes disconnected player ID from tracking.
|
||||
"""
|
||||
@spec remove_disconnected(t(), integer()) :: t()
|
||||
def remove_disconnected(eim, char_id) do
|
||||
disconnected = List.delete(eim.disconnected, char_id)
|
||||
%{eim | disconnected: disconnected}
|
||||
end
|
||||
|
||||
@doc """
|
||||
Checks if a player is disconnected.
|
||||
"""
|
||||
@spec is_disconnected?(t(), integer()) :: boolean()
|
||||
def is_disconnected?(eim, char_id) do
|
||||
char_id in eim.disconnected
|
||||
end
|
||||
|
||||
# ============================================================================
|
||||
# Party Management
|
||||
# ============================================================================
|
||||
|
||||
@doc """
|
||||
Registers an entire party to the event.
|
||||
"""
|
||||
@spec register_party(t(), term(), integer()) :: t()
|
||||
def register_party(%{disposed: true} = eim, _, _), do: eim
|
||||
def register_party(eim, party, map_id) do
|
||||
# TODO: Get party members and register each
|
||||
eim
|
||||
end
|
||||
|
||||
@doc """
|
||||
Registers a squad (expedition) to the event.
|
||||
"""
|
||||
@spec register_squad(t(), term(), integer(), integer()) :: t()
|
||||
def register_squad(%{disposed: true} = eim, _, _, _), do: eim
|
||||
def register_squad(eim, squad, map_id, quest_id) do
|
||||
# TODO: Register squad members
|
||||
eim
|
||||
end
|
||||
|
||||
@doc """
|
||||
Handles player leaving party.
|
||||
"""
|
||||
@spec left_party(t(), Character.t() | integer()) :: t()
|
||||
def left_party(%{disposed: true} = eim, _), do: eim
|
||||
def left_party(eim, player) do
|
||||
call_callback(eim, :left_party, [player])
|
||||
end
|
||||
|
||||
@doc """
|
||||
Handles party disbanding.
|
||||
"""
|
||||
@spec disband_party(t()) :: t()
|
||||
def disband_party(%{disposed: true} = eim), do: eim
|
||||
def disband_party(eim) do
|
||||
call_callback(eim, :disband_party, [])
|
||||
end
|
||||
|
||||
# ============================================================================
|
||||
# Monster Management
|
||||
# ============================================================================
|
||||
|
||||
@doc """
|
||||
Registers a monster to this event.
|
||||
"""
|
||||
@spec register_monster(t(), integer()) :: t()
|
||||
def register_monster(%{disposed: true} = eim, _), do: eim
|
||||
def register_monster(eim, mob_id) do
|
||||
monsters = [mob_id | eim.monsters]
|
||||
%{eim | monsters: monsters}
|
||||
end
|
||||
|
||||
@doc """
|
||||
Unregisters a monster when killed.
|
||||
"""
|
||||
@spec unregister_monster(t(), integer()) :: t()
|
||||
def unregister_monster(%{disposed: true} = eim, _), do: eim
|
||||
def unregister_monster(eim, mob_id) do
|
||||
monsters = List.delete(eim.monsters, mob_id)
|
||||
eim = %{eim | monsters: monsters}
|
||||
|
||||
# If no monsters left, call allMonstersDead
|
||||
if length(monsters) == 0 do
|
||||
call_callback(eim, :all_monsters_dead, [])
|
||||
else
|
||||
eim
|
||||
end
|
||||
end
|
||||
|
||||
@doc """
|
||||
Records monster kill and distributes points.
|
||||
"""
|
||||
@spec monster_killed(t(), Character.t() | integer(), integer()) :: t()
|
||||
def monster_killed(%{disposed: true} = eim, _, _), do: eim
|
||||
def monster_killed(eim, player, mob_id) do
|
||||
# Get monster value from script
|
||||
inc = call_callback_result(eim, :monster_value, [mob_id])
|
||||
inc = if is_integer(inc), do: inc, else: 0
|
||||
|
||||
# Update kill count
|
||||
char_id = case player do
|
||||
%{id: id} -> id
|
||||
id when is_integer(id) -> id
|
||||
end
|
||||
|
||||
current = Map.get(eim.kill_count, char_id, 0)
|
||||
kill_count = Map.put(eim.kill_count, char_id, current + inc)
|
||||
|
||||
%{eim | kill_count: kill_count}
|
||||
end
|
||||
|
||||
@doc """
|
||||
Gets kill count for a player.
|
||||
"""
|
||||
@spec get_kill_count(t(), integer()) :: integer()
|
||||
def get_kill_count(eim, char_id) do
|
||||
Map.get(eim.kill_count, char_id, 0)
|
||||
end
|
||||
|
||||
# ============================================================================
|
||||
# Timer Management
|
||||
# ============================================================================
|
||||
|
||||
@doc """
|
||||
Starts/restarts the event timer.
|
||||
|
||||
## Parameters
|
||||
- `eim` - Event instance
|
||||
- `time_ms` - Time in milliseconds
|
||||
"""
|
||||
@spec start_event_timer(t(), integer()) :: t()
|
||||
def start_event_timer(eim, time_ms) do
|
||||
restart_event_timer(eim, time_ms)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Restarts the event timer.
|
||||
"""
|
||||
@spec restart_event_timer(t(), integer()) :: t()
|
||||
def restart_event_timer(%{disposed: true} = eim, _), do: eim
|
||||
def restart_event_timer(eim, time_ms) do
|
||||
# Send clock packet to all players
|
||||
time_seconds = div(time_ms, 1000)
|
||||
broadcast_packet(eim, {:clock, time_seconds})
|
||||
|
||||
# Schedule timeout
|
||||
if eim.event_manager do
|
||||
Odinsea.Scripting.EventManager.schedule(
|
||||
eim,
|
||||
"scheduledTimeout",
|
||||
time_ms
|
||||
)
|
||||
end
|
||||
|
||||
%{eim |
|
||||
timer_started: true,
|
||||
time_started: System.system_time(:millisecond),
|
||||
event_time: time_ms
|
||||
}
|
||||
end
|
||||
|
||||
@doc """
|
||||
Stops the event timer.
|
||||
"""
|
||||
@spec stop_event_timer(t()) :: t()
|
||||
def stop_event_timer(eim) do
|
||||
%{eim |
|
||||
timer_started: false,
|
||||
time_started: nil,
|
||||
event_time: nil
|
||||
}
|
||||
end
|
||||
|
||||
@doc """
|
||||
Checks if timer is started.
|
||||
"""
|
||||
@spec is_timer_started?(t()) :: boolean()
|
||||
def is_timer_started?(eim) do
|
||||
eim.timer_started && eim.time_started != nil
|
||||
end
|
||||
|
||||
@doc """
|
||||
Gets time remaining in milliseconds.
|
||||
"""
|
||||
@spec get_time_left(t()) :: integer()
|
||||
def get_time_left(eim) do
|
||||
if is_timer_started?(eim) do
|
||||
elapsed = System.system_time(:millisecond) - eim.time_started
|
||||
max(0, eim.event_time - elapsed)
|
||||
else
|
||||
0
|
||||
end
|
||||
end
|
||||
|
||||
@doc """
|
||||
Schedules a custom method callback.
|
||||
"""
|
||||
@spec schedule(t(), String.t(), integer()) :: reference()
|
||||
def schedule(eim, method_name, delay_ms) do
|
||||
if eim.event_manager do
|
||||
Odinsea.Scripting.EventManager.schedule(eim, method_name, delay_ms)
|
||||
else
|
||||
nil
|
||||
end
|
||||
end
|
||||
|
||||
# ============================================================================
|
||||
# Map Instance Management
|
||||
# ============================================================================
|
||||
|
||||
@doc """
|
||||
Creates an instanced map (clone for PQ).
|
||||
|
||||
## Returns
|
||||
- `{map_instance_id, updated_eim}`
|
||||
"""
|
||||
@spec create_instance_map(t(), integer()) :: {integer(), t()}
|
||||
def create_instance_map(%{disposed: true} = eim, _), do: {0, eim}
|
||||
def create_instance_map(eim, map_id) do
|
||||
assigned_id = get_new_instance_map_id()
|
||||
|
||||
# TODO: Create actual map instance
|
||||
# For now, just track the ID
|
||||
eim = %{eim |
|
||||
map_ids: [assigned_id | eim.map_ids],
|
||||
is_instanced: [true | eim.is_instanced]
|
||||
}
|
||||
|
||||
{assigned_id, eim}
|
||||
end
|
||||
|
||||
@doc """
|
||||
Creates an instanced map with simplified settings.
|
||||
"""
|
||||
@spec create_instance_map_s(t(), integer()) :: {integer(), t()}
|
||||
def create_instance_map_s(eim, map_id) do
|
||||
create_instance_map(eim, map_id)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Sets an existing map as part of this event.
|
||||
"""
|
||||
@spec set_instance_map(t(), integer()) :: t()
|
||||
def set_instance_map(%{disposed: true} = eim, _), do: eim
|
||||
def set_instance_map(eim, map_id) do
|
||||
%{eim |
|
||||
map_ids: [map_id | eim.map_ids],
|
||||
is_instanced: [false | eim.is_instanced]
|
||||
}
|
||||
end
|
||||
|
||||
@doc """
|
||||
Gets a map instance by index.
|
||||
"""
|
||||
@spec get_map_instance(t(), integer()) :: term()
|
||||
def get_map_instance(eim, index) when index < length(eim.map_ids) do
|
||||
map_id = Enum.at(eim.map_ids, index)
|
||||
is_instanced = Enum.at(eim.is_instanced, index)
|
||||
|
||||
# TODO: Return actual map
|
||||
%{id: map_id, instanced: is_instanced}
|
||||
end
|
||||
def get_map_instance(eim, map_id) when is_integer(map_id) do
|
||||
# Assume it's a real map ID
|
||||
%{id: map_id, instanced: false}
|
||||
end
|
||||
|
||||
# ============================================================================
|
||||
# Properties
|
||||
# ============================================================================
|
||||
|
||||
@doc """
|
||||
Sets a property on this instance.
|
||||
"""
|
||||
@spec set_property(t(), String.t(), String.t()) :: t()
|
||||
def set_property(%{disposed: true} = eim, _, _), do: eim
|
||||
def set_property(eim, key, value) do
|
||||
properties = Map.put(eim.properties, key, value)
|
||||
%{eim | properties: properties}
|
||||
end
|
||||
|
||||
@doc """
|
||||
Gets a property value.
|
||||
"""
|
||||
@spec get_property(t(), String.t()) :: String.t() | nil
|
||||
def get_property(eim, key) do
|
||||
Map.get(eim.properties, key)
|
||||
end
|
||||
|
||||
# ============================================================================
|
||||
# Player Actions
|
||||
# ============================================================================
|
||||
|
||||
@doc """
|
||||
Removes a player from the event (warp out).
|
||||
"""
|
||||
@spec remove_player(t(), Character.t() | integer()) :: t()
|
||||
def remove_player(%{disposed: true} = eim, _), do: eim
|
||||
def remove_player(eim, player) do
|
||||
call_callback(eim, :player_exit, [player])
|
||||
end
|
||||
|
||||
@doc """
|
||||
Finishes the party quest.
|
||||
"""
|
||||
@spec finish_pq(t()) :: t()
|
||||
def finish_pq(%{disposed: true} = eim), do: eim
|
||||
def finish_pq(eim) do
|
||||
call_callback(eim, :clear_pq, [])
|
||||
end
|
||||
|
||||
@doc """
|
||||
Awards achievement to all players.
|
||||
"""
|
||||
@spec give_achievement(t(), integer()) :: :ok
|
||||
def give_achievement(eim, type) do
|
||||
broadcast_to_players(eim, {:achievement, type})
|
||||
:ok
|
||||
end
|
||||
|
||||
@doc """
|
||||
Broadcasts a message to all players in the event.
|
||||
"""
|
||||
@spec broadcast_player_msg(t(), integer(), String.t()) :: :ok
|
||||
def broadcast_player_msg(eim, type, message) do
|
||||
broadcast_to_players(eim, {:message, type, message})
|
||||
:ok
|
||||
end
|
||||
|
||||
@doc """
|
||||
Broadcasts a raw packet to all players.
|
||||
"""
|
||||
@spec broadcast_packet(t(), term()) :: :ok
|
||||
def broadcast_packet(eim, packet) do
|
||||
Enum.each(eim.players, fn char_id ->
|
||||
# TODO: Send packet to player
|
||||
:ok
|
||||
end)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Broadcasts packet to team members.
|
||||
"""
|
||||
@spec broadcast_team_packet(t(), term(), integer()) :: :ok
|
||||
def broadcast_team_packet(eim, packet, team) do
|
||||
# TODO: Filter by team and send
|
||||
:ok
|
||||
end
|
||||
|
||||
@doc """
|
||||
Applies buff to a player.
|
||||
"""
|
||||
@spec apply_buff(t(), Character.t() | integer(), integer()) :: :ok
|
||||
def apply_buff(eim, player, buff_id) do
|
||||
# TODO: Apply item effect
|
||||
:ok
|
||||
end
|
||||
|
||||
@doc """
|
||||
Applies skill to a player.
|
||||
"""
|
||||
@spec apply_skill(t(), Character.t() | integer(), integer()) :: :ok
|
||||
def apply_skill(eim, player, skill_id) do
|
||||
# TODO: Apply skill effect
|
||||
:ok
|
||||
end
|
||||
|
||||
# ============================================================================
|
||||
# Carnival Party (CPQ)
|
||||
# ============================================================================
|
||||
|
||||
@doc """
|
||||
Registers a carnival party.
|
||||
"""
|
||||
@spec register_carnival_party(t(), term()) :: t()
|
||||
def register_carnival_party(%{disposed: true} = eim, _), do: eim
|
||||
def register_carnival_party(eim, carnival_party) do
|
||||
call_callback(eim, :register_carnival_party, [carnival_party])
|
||||
end
|
||||
|
||||
# ============================================================================
|
||||
# Disposal
|
||||
# ============================================================================
|
||||
|
||||
@doc """
|
||||
Disposes the event instance if player count is at or below threshold.
|
||||
|
||||
## Returns
|
||||
- `{true, eim}` - Instance was disposed
|
||||
- `{false, eim}` - Instance not disposed
|
||||
"""
|
||||
@spec dispose_if_player_below(t(), integer(), integer()) :: {boolean(), t()}
|
||||
def dispose_if_player_below(%{disposed: true} = eim, _, _), do: {true, eim}
|
||||
def dispose_if_player_below(eim, size, warp_map_id) do
|
||||
if length(eim.players) <= size do
|
||||
# Warp players if map specified
|
||||
if warp_map_id > 0 do
|
||||
# TODO: Warp all players
|
||||
end
|
||||
|
||||
{true, dispose(eim)}
|
||||
else
|
||||
{false, eim}
|
||||
end
|
||||
end
|
||||
|
||||
@doc """
|
||||
Disposes the event instance.
|
||||
"""
|
||||
@spec dispose(t()) :: t()
|
||||
def dispose(%{disposed: true} = eim), do: eim
|
||||
def dispose(eim) do
|
||||
# Clear player event instances
|
||||
Enum.each(eim.players, fn char_id ->
|
||||
# TODO: Clear player's event instance reference
|
||||
:ok
|
||||
end)
|
||||
|
||||
# Remove instanced maps
|
||||
Enum.zip(eim.map_ids, eim.is_instanced)
|
||||
|> Enum.each(fn {map_id, instanced} ->
|
||||
if instanced do
|
||||
# TODO: Remove instance map
|
||||
:ok
|
||||
end
|
||||
end)
|
||||
|
||||
# Notify event manager
|
||||
if eim.event_manager do
|
||||
Odinsea.Scripting.EventManager.dispose_instance(eim.name)
|
||||
end
|
||||
|
||||
%{eim |
|
||||
disposed: true,
|
||||
players: [],
|
||||
monsters: [],
|
||||
kill_count: %{},
|
||||
map_ids: [],
|
||||
is_instanced: []
|
||||
}
|
||||
end
|
||||
|
||||
# ============================================================================
|
||||
# Utility
|
||||
# ============================================================================
|
||||
|
||||
@doc """
|
||||
Checks if player is the leader.
|
||||
"""
|
||||
@spec is_leader?(t(), Character.t() | integer()) :: boolean()
|
||||
def is_leader?(_eim, _player) do
|
||||
# TODO: Check party leadership
|
||||
false
|
||||
end
|
||||
|
||||
@doc """
|
||||
Gets player count.
|
||||
"""
|
||||
@spec get_player_count(t()) :: integer()
|
||||
def get_player_count(%{disposed: true}), do: 0
|
||||
def get_player_count(eim), do: length(eim.players)
|
||||
|
||||
@doc """
|
||||
Gets the list of players.
|
||||
"""
|
||||
@spec get_players(t()) :: [integer()]
|
||||
def get_players(%{disposed: true}), do: []
|
||||
def get_players(eim), do: eim.players
|
||||
|
||||
@doc """
|
||||
Gets the list of monsters.
|
||||
"""
|
||||
@spec get_mobs(t()) :: [integer()]
|
||||
def get_mobs(eim), do: eim.monsters
|
||||
|
||||
@doc """
|
||||
Handles map load event.
|
||||
"""
|
||||
@spec on_map_load(t(), Character.t() | integer()) :: t()
|
||||
def on_map_load(%{disposed: true} = eim, _), do: eim
|
||||
def on_map_load(eim, player) do
|
||||
call_callback(eim, :on_map_load, [player])
|
||||
end
|
||||
|
||||
@doc """
|
||||
Creates a new pair list (utility for scripts).
|
||||
"""
|
||||
@spec new_pair() :: list()
|
||||
def new_pair(), do: []
|
||||
|
||||
@doc """
|
||||
Adds to a pair list.
|
||||
"""
|
||||
@spec add_to_pair(list(), term(), term()) :: list()
|
||||
def add_to_pair(list, key, value) do
|
||||
[{key, value} | list]
|
||||
end
|
||||
|
||||
@doc """
|
||||
Creates a new character pair list.
|
||||
"""
|
||||
@spec new_pair_chr() :: list()
|
||||
def new_pair_chr(), do: []
|
||||
|
||||
@doc """
|
||||
Adds to a character pair list.
|
||||
"""
|
||||
@spec add_to_pair_chr(list(), term(), term()) :: list()
|
||||
def add_to_pair_chr(list, key, value) do
|
||||
[{key, value} | list]
|
||||
end
|
||||
|
||||
# ============================================================================
|
||||
# Private Functions
|
||||
# ============================================================================
|
||||
|
||||
defp call_callback(%{disposed: true} = eim, _method, _args), do: eim
|
||||
defp call_callback(eim, method, args) do
|
||||
if eim.event_manager && eim.event_manager.script_module do
|
||||
mod = eim.event_manager.script_module
|
||||
|
||||
if function_exported?(mod, method, length(args) + 1) do
|
||||
try do
|
||||
apply(mod, method, [eim | args])
|
||||
rescue
|
||||
e ->
|
||||
Logger.error("Event callback #{method} error: #{inspect(e)}")
|
||||
eim
|
||||
end
|
||||
else
|
||||
eim
|
||||
end
|
||||
else
|
||||
eim
|
||||
end
|
||||
end
|
||||
|
||||
defp call_callback_result(eim, method, args) do
|
||||
if eim.event_manager && eim.event_manager.script_module do
|
||||
mod = eim.event_manager.script_module
|
||||
|
||||
if function_exported?(mod, method, length(args) + 1) do
|
||||
try do
|
||||
apply(mod, method, [eim | args])
|
||||
rescue
|
||||
e ->
|
||||
Logger.error("Event callback #{method} error: #{inspect(e)}")
|
||||
nil
|
||||
end
|
||||
else
|
||||
nil
|
||||
end
|
||||
else
|
||||
nil
|
||||
end
|
||||
end
|
||||
|
||||
defp broadcast_to_players(_eim, _message) do
|
||||
# TODO: Implement broadcasting
|
||||
:ok
|
||||
end
|
||||
|
||||
# Global counter for instance map IDs
|
||||
defp get_new_instance_map_id() do
|
||||
# Use persistent_term or similar for atomic increment
|
||||
:counters.add(:instance_counter, 1, 1)
|
||||
rescue
|
||||
_ ->
|
||||
# Fallback if counter doesn't exist
|
||||
System.unique_integer([:positive])
|
||||
end
|
||||
end
|
||||
Reference in New Issue
Block a user