253 lines
6.4 KiB
Elixir
253 lines
6.4 KiB
Elixir
defmodule Odinsea.Game.ReactorStats do
|
|
@moduledoc """
|
|
Represents reactor template stats (state machine data).
|
|
|
|
Contains the state definitions for a reactor type.
|
|
Each state defines: type, next state, required item, timeout, touch mode.
|
|
|
|
Ported from Java: src/server/maps/MapleReactorStats.java
|
|
"""
|
|
|
|
defmodule Point do
|
|
@moduledoc "Simple 2D point for area bounds"
|
|
@type t :: %__MODULE__{x: integer(), y: integer()}
|
|
defstruct [:x, :y]
|
|
end
|
|
|
|
defmodule StateData do
|
|
@moduledoc "State definition for a reactor"
|
|
@type t :: %__MODULE__{
|
|
type: integer(), # State type (determines behavior)
|
|
next_state: integer(), # Next state index (-1 = end)
|
|
react_item: {integer(), integer()} | nil, # {item_id, quantity} required
|
|
timeout: integer(), # Timeout in ms before auto-advance (-1 = none)
|
|
can_touch: integer() # 0 = hit only, 1 = click/touch, 2 = touch only
|
|
}
|
|
defstruct [
|
|
:type,
|
|
:next_state,
|
|
:react_item,
|
|
timeout: -1,
|
|
can_touch: 0
|
|
]
|
|
end
|
|
|
|
@typedoc "Reactor stats struct"
|
|
@type t :: %__MODULE__{
|
|
tl: Point.t() | nil, # Top-left corner of area (for item-triggered)
|
|
br: Point.t() | nil, # Bottom-right corner of area
|
|
states: %{integer() => StateData.t()}, # State definitions by state number
|
|
activate_by_touch: boolean() # Whether reactor activates by touch
|
|
}
|
|
|
|
defstruct [
|
|
:tl,
|
|
:br,
|
|
states: %{},
|
|
activate_by_touch: false
|
|
]
|
|
|
|
@doc """
|
|
Creates a new empty reactor stats.
|
|
"""
|
|
@spec new() :: t()
|
|
def new do
|
|
%__MODULE__{}
|
|
end
|
|
|
|
@doc """
|
|
Sets the top-left point of the area.
|
|
"""
|
|
@spec set_tl(t(), integer(), integer()) :: t()
|
|
def set_tl(stats, x, y) do
|
|
%{stats | tl: %Point{x: x, y: y}}
|
|
end
|
|
|
|
@doc """
|
|
Sets the bottom-right point of the area.
|
|
"""
|
|
@spec set_br(t(), integer(), integer()) :: t()
|
|
def set_br(stats, x, y) do
|
|
%{stats | br: %Point{x: x, y: y}}
|
|
end
|
|
|
|
@doc """
|
|
Sets whether reactor activates by touch.
|
|
"""
|
|
@spec set_activate_by_touch(t(), boolean()) :: t()
|
|
def set_activate_by_touch(stats, activate) do
|
|
%{stats | activate_by_touch: activate}
|
|
end
|
|
|
|
@doc """
|
|
Adds a state definition.
|
|
|
|
## Parameters
|
|
- stats: the reactor stats struct
|
|
- state_num: the state number (byte value)
|
|
- type: the state type (determines behavior)
|
|
- react_item: {item_id, quantity} or nil
|
|
- next_state: the next state number (-1 for end)
|
|
- timeout: timeout in ms (-1 for none)
|
|
- can_touch: 0 = hit only, 1 = click, 2 = touch only
|
|
"""
|
|
@spec add_state(
|
|
t(),
|
|
integer(),
|
|
integer(),
|
|
{integer(), integer()} | nil,
|
|
integer(),
|
|
integer(),
|
|
integer()
|
|
) :: t()
|
|
def add_state(stats, state_num, type, react_item, next_state, timeout, can_touch) do
|
|
state_data = %StateData{
|
|
type: type,
|
|
react_item: react_item,
|
|
next_state: next_state,
|
|
timeout: timeout,
|
|
can_touch: can_touch
|
|
}
|
|
|
|
%{stats | states: Map.put(stats.states, state_num, state_data)}
|
|
end
|
|
|
|
@doc """
|
|
Gets the next state for a given current state.
|
|
Returns -1 if not found.
|
|
"""
|
|
@spec get_next_state(t(), integer()) :: integer()
|
|
def get_next_state(stats, state) do
|
|
case Map.get(stats.states, state) do
|
|
nil -> -1
|
|
state_data -> state_data.next_state
|
|
end
|
|
end
|
|
|
|
@doc """
|
|
Gets the type for a given state.
|
|
Returns -1 if not found.
|
|
"""
|
|
@spec get_type(t(), integer()) :: integer()
|
|
def get_type(stats, state) do
|
|
case Map.get(stats.states, state) do
|
|
nil -> -1
|
|
state_data -> state_data.type
|
|
end
|
|
end
|
|
|
|
@doc """
|
|
Gets the react item for a given state.
|
|
Returns nil if not found.
|
|
"""
|
|
@spec get_react_item(t(), integer()) :: {integer(), integer()} | nil
|
|
def get_react_item(stats, state) do
|
|
case Map.get(stats.states, state) do
|
|
nil -> nil
|
|
state_data -> state_data.react_item
|
|
end
|
|
end
|
|
|
|
@doc """
|
|
Gets the timeout for a given state.
|
|
Returns -1 if not found.
|
|
"""
|
|
@spec get_timeout(t(), integer()) :: integer()
|
|
def get_timeout(stats, state) do
|
|
case Map.get(stats.states, state) do
|
|
nil -> -1
|
|
state_data -> state_data.timeout
|
|
end
|
|
end
|
|
|
|
@doc """
|
|
Gets the touch mode for a given state.
|
|
Returns 0 if not found.
|
|
|
|
Modes:
|
|
- 0: Hit only (weapon attack)
|
|
- 1: Click/touch (interact button)
|
|
- 2: Touch only (walk into)
|
|
"""
|
|
@spec can_touch(t(), integer()) :: integer()
|
|
def can_touch(stats, state) do
|
|
case Map.get(stats.states, state) do
|
|
nil -> 0
|
|
state_data -> state_data.can_touch
|
|
end
|
|
end
|
|
|
|
@doc """
|
|
Gets all state numbers defined for this reactor.
|
|
"""
|
|
@spec get_state_numbers(t()) :: [integer()]
|
|
def get_state_numbers(stats) do
|
|
Map.keys(stats.states) |> Enum.sort()
|
|
end
|
|
|
|
@doc """
|
|
Checks if a state exists.
|
|
"""
|
|
@spec has_state?(t(), integer()) :: boolean()
|
|
def has_state?(stats, state) do
|
|
Map.has_key?(stats.states, state)
|
|
end
|
|
|
|
@doc """
|
|
Gets the state data for a given state number.
|
|
"""
|
|
@spec get_state_data(t(), integer()) :: StateData.t() | nil
|
|
def get_state_data(stats, state) do
|
|
Map.get(stats.states, state)
|
|
end
|
|
|
|
@doc """
|
|
Builds reactor stats from JSON data.
|
|
"""
|
|
@spec from_json(map()) :: t()
|
|
def from_json(data) do
|
|
stats = new()
|
|
|
|
# Set activate by touch
|
|
stats = set_activate_by_touch(stats, data["activate_by_touch"] == true)
|
|
|
|
# Set area bounds if present
|
|
stats =
|
|
if data["tl"] do
|
|
set_tl(stats, data["tl"]["x"] || 0, data["tl"]["y"] || 0)
|
|
else
|
|
stats
|
|
end
|
|
|
|
stats =
|
|
if data["br"] do
|
|
set_br(stats, data["br"]["x"] || 0, data["br"]["y"] || 0)
|
|
else
|
|
stats
|
|
end
|
|
|
|
# Add states
|
|
states = data["states"] || %{}
|
|
|
|
Enum.reduce(states, stats, fn {state_num_str, state_data}, acc_stats ->
|
|
state_num = String.to_integer(state_num_str)
|
|
|
|
type = state_data["type"] || 999
|
|
next_state = state_data["next_state"] || -1
|
|
timeout = state_data["timeout"] || -1
|
|
can_touch = state_data["can_touch"] || 0
|
|
|
|
react_item =
|
|
if state_data["react_item"] do
|
|
item_id = state_data["react_item"]["item_id"]
|
|
quantity = state_data["react_item"]["quantity"] || 1
|
|
if item_id, do: {item_id, quantity}, else: nil
|
|
else
|
|
nil
|
|
end
|
|
|
|
add_state(acc_stats, state_num, type, react_item, next_state, timeout, can_touch)
|
|
end)
|
|
end
|
|
end
|