788 lines
21 KiB
Elixir
788 lines
21 KiB
Elixir
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
|