kimi gone wild
This commit is contained in:
284
lib/odinsea/game/reactor.ex
Normal file
284
lib/odinsea/game/reactor.ex
Normal file
@@ -0,0 +1,284 @@
|
||||
defmodule Odinsea.Game.Reactor do
|
||||
@moduledoc """
|
||||
Represents a reactor instance on a map.
|
||||
|
||||
Reactors are map objects (boxes, rocks, plants) that can be hit/activated by players.
|
||||
They have states, can drop items, trigger scripts, and respawn after time.
|
||||
|
||||
Ported from Java: src/server/maps/MapleReactor.java
|
||||
"""
|
||||
|
||||
alias Odinsea.Game.ReactorStats
|
||||
|
||||
@typedoc "Reactor instance struct"
|
||||
@type t :: %__MODULE__{
|
||||
# Identity
|
||||
oid: integer() | nil, # Object ID (assigned by map)
|
||||
reactor_id: integer(), # Reactor template ID
|
||||
|
||||
# State
|
||||
state: integer(), # Current state (byte)
|
||||
alive: boolean(), # Whether reactor is active
|
||||
timer_active: boolean(), # Whether timeout timer is running
|
||||
|
||||
# Position
|
||||
x: integer(), # X position
|
||||
y: integer(), # Y position
|
||||
facing_direction: integer(), # Facing direction (0 or 1)
|
||||
|
||||
# Properties
|
||||
name: String.t(), # Reactor name
|
||||
delay: integer(), # Respawn delay in milliseconds
|
||||
custom: boolean(), # Custom spawned (not from template)
|
||||
|
||||
# Stats reference
|
||||
stats: ReactorStats.t() | nil # Template stats
|
||||
}
|
||||
|
||||
defstruct [
|
||||
:oid,
|
||||
:reactor_id,
|
||||
:stats,
|
||||
state: 0,
|
||||
alive: true,
|
||||
timer_active: false,
|
||||
x: 0,
|
||||
y: 0,
|
||||
facing_direction: 0,
|
||||
name: "",
|
||||
delay: -1,
|
||||
custom: false
|
||||
]
|
||||
|
||||
@doc """
|
||||
Creates a new reactor instance from template stats.
|
||||
"""
|
||||
@spec new(integer(), ReactorStats.t()) :: t()
|
||||
def new(reactor_id, stats) do
|
||||
%__MODULE__{
|
||||
reactor_id: reactor_id,
|
||||
stats: stats
|
||||
}
|
||||
end
|
||||
|
||||
@doc """
|
||||
Creates a copy of a reactor (for respawning).
|
||||
"""
|
||||
@spec copy(t()) :: t()
|
||||
def copy(reactor) do
|
||||
%__MODULE__{
|
||||
reactor_id: reactor.reactor_id,
|
||||
stats: reactor.stats,
|
||||
state: 0,
|
||||
alive: true,
|
||||
timer_active: false,
|
||||
x: reactor.x,
|
||||
y: reactor.y,
|
||||
facing_direction: reactor.facing_direction,
|
||||
name: reactor.name,
|
||||
delay: reactor.delay,
|
||||
custom: reactor.custom
|
||||
}
|
||||
end
|
||||
|
||||
@doc """
|
||||
Sets the reactor's object ID.
|
||||
"""
|
||||
@spec set_oid(t(), integer()) :: t()
|
||||
def set_oid(reactor, oid) do
|
||||
%{reactor | oid: oid}
|
||||
end
|
||||
|
||||
@doc """
|
||||
Sets the reactor's position.
|
||||
"""
|
||||
@spec set_position(t(), integer(), integer()) :: t()
|
||||
def set_position(reactor, x, y) do
|
||||
%{reactor | x: x, y: y}
|
||||
end
|
||||
|
||||
@doc """
|
||||
Sets the reactor's state.
|
||||
"""
|
||||
@spec set_state(t(), integer()) :: t()
|
||||
def set_state(reactor, state) do
|
||||
%{reactor | state: state}
|
||||
end
|
||||
|
||||
@doc """
|
||||
Sets whether the reactor is alive.
|
||||
"""
|
||||
@spec set_alive(t(), boolean()) :: t()
|
||||
def set_alive(reactor, alive) do
|
||||
%{reactor | alive: alive}
|
||||
end
|
||||
|
||||
@doc """
|
||||
Sets the facing direction.
|
||||
"""
|
||||
@spec set_facing_direction(t(), integer()) :: t()
|
||||
def set_facing_direction(reactor, direction) do
|
||||
%{reactor | facing_direction: direction}
|
||||
end
|
||||
|
||||
@doc """
|
||||
Sets the reactor name.
|
||||
"""
|
||||
@spec set_name(t(), String.t()) :: t()
|
||||
def set_name(reactor, name) do
|
||||
%{reactor | name: name}
|
||||
end
|
||||
|
||||
@doc """
|
||||
Sets the respawn delay.
|
||||
"""
|
||||
@spec set_delay(t(), integer()) :: t()
|
||||
def set_delay(reactor, delay) do
|
||||
%{reactor | delay: delay}
|
||||
end
|
||||
|
||||
@doc """
|
||||
Sets whether this is a custom reactor.
|
||||
"""
|
||||
@spec set_custom(t(), boolean()) :: t()
|
||||
def set_custom(reactor, custom) do
|
||||
%{reactor | custom: custom}
|
||||
end
|
||||
|
||||
@doc """
|
||||
Sets timer active status.
|
||||
"""
|
||||
@spec set_timer_active(t(), boolean()) :: t()
|
||||
def set_timer_active(reactor, active) do
|
||||
%{reactor | timer_active: active}
|
||||
end
|
||||
|
||||
@doc """
|
||||
Gets the reactor type for the current state.
|
||||
Returns the type value or -1 if stats not loaded.
|
||||
"""
|
||||
@spec get_type(t()) :: integer()
|
||||
def get_type(reactor) do
|
||||
if reactor.stats do
|
||||
ReactorStats.get_type(reactor.stats, reactor.state)
|
||||
else
|
||||
-1
|
||||
end
|
||||
end
|
||||
|
||||
@doc """
|
||||
Gets the next state for the current state.
|
||||
"""
|
||||
@spec get_next_state(t()) :: integer()
|
||||
def get_next_state(reactor) do
|
||||
if reactor.stats do
|
||||
ReactorStats.get_next_state(reactor.stats, reactor.state)
|
||||
else
|
||||
-1
|
||||
end
|
||||
end
|
||||
|
||||
@doc """
|
||||
Gets the timeout for the current state.
|
||||
"""
|
||||
@spec get_timeout(t()) :: integer()
|
||||
def get_timeout(reactor) do
|
||||
if reactor.stats do
|
||||
ReactorStats.get_timeout(reactor.stats, reactor.state)
|
||||
else
|
||||
-1
|
||||
end
|
||||
end
|
||||
|
||||
@doc """
|
||||
Gets the touch mode for the current state.
|
||||
Returns: 0 = hit only, 1 = click/touch, 2 = touch only
|
||||
"""
|
||||
@spec can_touch(t()) :: integer()
|
||||
def can_touch(reactor) do
|
||||
if reactor.stats do
|
||||
ReactorStats.can_touch(reactor.stats, reactor.state)
|
||||
else
|
||||
0
|
||||
end
|
||||
end
|
||||
|
||||
@doc """
|
||||
Gets the required item to react for the current state.
|
||||
Returns {item_id, quantity} or nil.
|
||||
"""
|
||||
@spec get_react_item(t()) :: {integer(), integer()} | nil
|
||||
def get_react_item(reactor) do
|
||||
if reactor.stats do
|
||||
ReactorStats.get_react_item(reactor.stats, reactor.state)
|
||||
else
|
||||
nil
|
||||
end
|
||||
end
|
||||
|
||||
@doc """
|
||||
Advances to the next state.
|
||||
Returns the updated reactor.
|
||||
"""
|
||||
@spec advance_state(t()) :: t()
|
||||
def advance_state(reactor) do
|
||||
next_state = get_next_state(reactor)
|
||||
if next_state >= 0 do
|
||||
set_state(reactor, next_state)
|
||||
else
|
||||
reactor
|
||||
end
|
||||
end
|
||||
|
||||
@doc """
|
||||
Checks if the reactor should trigger a script for the current state.
|
||||
"""
|
||||
@spec should_trigger_script?(t()) :: boolean()
|
||||
def should_trigger_script?(reactor) do
|
||||
type = get_type(reactor)
|
||||
# Type < 100 or type == 999 typically trigger scripts
|
||||
type < 100 or type == 999
|
||||
end
|
||||
|
||||
@doc """
|
||||
Checks if this reactor is in a looping state (state == next_state).
|
||||
"""
|
||||
@spec is_looping?(t()) :: boolean()
|
||||
def is_looping?(reactor) do
|
||||
reactor.state == get_next_state(reactor)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Checks if the reactor should be destroyed (next state is -1 or final state).
|
||||
"""
|
||||
@spec should_destroy?(t()) :: boolean()
|
||||
def should_destroy?(reactor) do
|
||||
next = get_next_state(reactor)
|
||||
next == -1 or get_type(reactor) == 999
|
||||
end
|
||||
|
||||
@doc """
|
||||
Gets the reactor's area of effect (hit box).
|
||||
Returns {tl_x, tl_y, br_x, br_y} or nil if not defined.
|
||||
"""
|
||||
@spec get_area(t()) :: {integer(), integer(), integer(), integer()} | nil
|
||||
def get_area(reactor) do
|
||||
if reactor.stats and reactor.stats.tl and reactor.stats.br do
|
||||
{reactor.stats.tl.x, reactor.stats.tl.y, reactor.stats.br.x, reactor.stats.br.y}
|
||||
else
|
||||
nil
|
||||
end
|
||||
end
|
||||
|
||||
@doc """
|
||||
Resets the reactor to initial state (for respawning).
|
||||
"""
|
||||
@spec reset(t()) :: t()
|
||||
def reset(reactor) do
|
||||
%{reactor |
|
||||
state: 0,
|
||||
alive: true,
|
||||
timer_active: false
|
||||
}
|
||||
end
|
||||
end
|
||||
Reference in New Issue
Block a user