kimi gone wild

This commit is contained in:
ra
2026-02-14 23:12:33 -07:00
parent bbd205ecbe
commit 0222be36c5
98 changed files with 39726 additions and 309 deletions

View File

@@ -0,0 +1,252 @@
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