kimi gone wild
This commit is contained in:
276
lib/odinsea/game/reactor_factory.ex
Normal file
276
lib/odinsea/game/reactor_factory.ex
Normal file
@@ -0,0 +1,276 @@
|
||||
defmodule Odinsea.Game.ReactorFactory do
|
||||
@moduledoc """
|
||||
Reactor Factory - loads and caches reactor template data.
|
||||
|
||||
This module loads reactor metadata (states, types, items, timeouts) from cached JSON files.
|
||||
The JSON files should be exported from the Java server's WZ data providers.
|
||||
|
||||
Reactor data is cached in ETS for fast lookups.
|
||||
|
||||
Ported from Java: src/server/maps/MapleReactorFactory.java
|
||||
"""
|
||||
|
||||
use GenServer
|
||||
require Logger
|
||||
|
||||
alias Odinsea.Game.{Reactor, ReactorStats}
|
||||
|
||||
# ETS table name
|
||||
@reactor_stats :odinsea_reactor_stats
|
||||
|
||||
# Data file path
|
||||
@reactor_data_file "data/reactors.json"
|
||||
|
||||
## Public API
|
||||
|
||||
@doc "Starts the ReactorFactory GenServer"
|
||||
def start_link(opts \\ []) do
|
||||
GenServer.start_link(__MODULE__, opts, name: __MODULE__)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Gets reactor stats by reactor ID.
|
||||
Returns nil if not found.
|
||||
"""
|
||||
@spec get_reactor_stats(integer()) :: ReactorStats.t() | nil
|
||||
def get_reactor_stats(reactor_id) do
|
||||
case :ets.lookup(@reactor_stats, reactor_id) do
|
||||
[{^reactor_id, stats}] -> stats
|
||||
[] -> nil
|
||||
end
|
||||
end
|
||||
|
||||
@doc """
|
||||
Gets a reactor instance by ID.
|
||||
Returns nil if stats not found.
|
||||
"""
|
||||
@spec get_reactor(integer()) :: Reactor.t() | nil
|
||||
def get_reactor(reactor_id) do
|
||||
case get_reactor_stats(reactor_id) do
|
||||
nil -> nil
|
||||
stats -> Reactor.new(reactor_id, stats)
|
||||
end
|
||||
end
|
||||
|
||||
@doc """
|
||||
Creates a reactor instance with position and properties.
|
||||
"""
|
||||
@spec create_reactor(integer(), integer(), integer(), integer(), String.t(), integer()) :: Reactor.t() | nil
|
||||
def create_reactor(reactor_id, x, y, facing_direction \\ 0, name \\ "", delay \\ -1) do
|
||||
case get_reactor_stats(reactor_id) do
|
||||
nil ->
|
||||
Logger.warning("Reactor stats not found for reactor_id=#{reactor_id}")
|
||||
nil
|
||||
|
||||
stats ->
|
||||
%Reactor{
|
||||
reactor_id: reactor_id,
|
||||
stats: stats,
|
||||
x: x,
|
||||
y: y,
|
||||
facing_direction: facing_direction,
|
||||
name: name,
|
||||
delay: delay,
|
||||
state: 0,
|
||||
alive: true,
|
||||
timer_active: false,
|
||||
custom: false
|
||||
}
|
||||
end
|
||||
end
|
||||
|
||||
@doc """
|
||||
Checks if reactor stats exist.
|
||||
"""
|
||||
@spec reactor_exists?(integer()) :: boolean()
|
||||
def reactor_exists?(reactor_id) do
|
||||
:ets.member(@reactor_stats, reactor_id)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Gets all loaded reactor IDs.
|
||||
"""
|
||||
@spec get_all_reactor_ids() :: [integer()]
|
||||
def get_all_reactor_ids do
|
||||
:ets.select(@reactor_stats, [{{:"$1", :_}, [], [:"$1"]}])
|
||||
end
|
||||
|
||||
@doc """
|
||||
Gets the number of loaded reactors.
|
||||
"""
|
||||
@spec get_reactor_count() :: integer()
|
||||
def get_reactor_count do
|
||||
:ets.info(@reactor_stats, :size)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Reloads reactor data from files.
|
||||
"""
|
||||
def reload do
|
||||
GenServer.call(__MODULE__, :reload, :infinity)
|
||||
end
|
||||
|
||||
## GenServer Callbacks
|
||||
|
||||
@impl true
|
||||
def init(_opts) do
|
||||
# Create ETS table
|
||||
:ets.new(@reactor_stats, [:set, :public, :named_table, read_concurrency: true])
|
||||
|
||||
# Load data
|
||||
load_reactor_data()
|
||||
|
||||
{:ok, %{}}
|
||||
end
|
||||
|
||||
@impl true
|
||||
def handle_call(:reload, _from, state) do
|
||||
Logger.info("Reloading reactor data...")
|
||||
load_reactor_data()
|
||||
{:reply, :ok, state}
|
||||
end
|
||||
|
||||
## Private Functions
|
||||
|
||||
defp load_reactor_data do
|
||||
priv_dir = :code.priv_dir(:odinsea) |> to_string()
|
||||
file_path = Path.join(priv_dir, @reactor_data_file)
|
||||
|
||||
load_reactors_from_file(file_path)
|
||||
|
||||
count = :ets.info(@reactor_stats, :size)
|
||||
Logger.info("Loaded #{count} reactor templates")
|
||||
end
|
||||
|
||||
defp load_reactors_from_file(file_path) do
|
||||
case File.read(file_path) do
|
||||
{:ok, content} ->
|
||||
case Jason.decode(content) do
|
||||
{:ok, reactors} when is_map(reactors) ->
|
||||
# Clear existing data
|
||||
:ets.delete_all_objects(@reactor_stats)
|
||||
|
||||
# Load reactors and handle links
|
||||
links = process_reactors(reactors, %{})
|
||||
|
||||
# Resolve links
|
||||
resolve_links(links)
|
||||
|
||||
:ok
|
||||
|
||||
{:error, reason} ->
|
||||
Logger.warning("Failed to parse reactors JSON: #{inspect(reason)}")
|
||||
create_fallback_reactors()
|
||||
end
|
||||
|
||||
{:error, :enoent} ->
|
||||
Logger.warning("Reactors file not found: #{file_path}, using fallback data")
|
||||
create_fallback_reactors()
|
||||
|
||||
{:error, reason} ->
|
||||
Logger.error("Failed to read reactors: #{inspect(reason)}")
|
||||
create_fallback_reactors()
|
||||
end
|
||||
end
|
||||
|
||||
defp process_reactors(reactors, links) do
|
||||
Enum.reduce(reactors, links, fn {reactor_id_str, reactor_data}, acc_links ->
|
||||
reactor_id = String.to_integer(reactor_id_str)
|
||||
|
||||
# Check if this is a link to another reactor
|
||||
link_target = reactor_data["link"]
|
||||
|
||||
if link_target && link_target > 0 do
|
||||
# Store link for later resolution
|
||||
Map.put(acc_links, reactor_id, link_target)
|
||||
else
|
||||
# Build stats from data
|
||||
stats = ReactorStats.from_json(reactor_data)
|
||||
:ets.insert(@reactor_stats, {reactor_id, stats})
|
||||
acc_links
|
||||
end
|
||||
end)
|
||||
end
|
||||
|
||||
defp resolve_links(links) do
|
||||
Enum.each(links, fn {reactor_id, target_id} ->
|
||||
case :ets.lookup(@reactor_stats, target_id) do
|
||||
[{^target_id, target_stats}] ->
|
||||
# Copy target stats for linked reactor
|
||||
:ets.insert(@reactor_stats, {reactor_id, target_stats})
|
||||
|
||||
[] ->
|
||||
Logger.warning("Link target not found: #{target_id} for reactor #{reactor_id}")
|
||||
end
|
||||
end)
|
||||
end
|
||||
|
||||
# Fallback data for basic testing
|
||||
defp create_fallback_reactors do
|
||||
# Common reactors from MapleStory
|
||||
fallback_reactors = [
|
||||
%{
|
||||
reactor_id: 100000, # Normal box
|
||||
states: %{
|
||||
"0" => %{type: 999, next_state: -1, timeout: -1, can_touch: 0},
|
||||
"1" => %{type: 999, next_state: -1, timeout: -1, can_touch: 0}
|
||||
}
|
||||
},
|
||||
%{
|
||||
reactor_id: 200000, # Herb
|
||||
activate_by_touch: true,
|
||||
states: %{
|
||||
"0" => %{type: 2, next_state: 1, timeout: -1, can_touch: 2},
|
||||
"1" => %{type: 999, next_state: -1, timeout: -1, can_touch: 0}
|
||||
}
|
||||
},
|
||||
%{
|
||||
reactor_id: 200100, # Vein
|
||||
activate_by_touch: true,
|
||||
states: %{
|
||||
"0" => %{type: 2, next_state: 1, timeout: -1, can_touch: 2},
|
||||
"1" => %{type: 999, next_state: -1, timeout: -1, can_touch: 0}
|
||||
}
|
||||
},
|
||||
%{
|
||||
reactor_id: 200200, # Gold Flower
|
||||
activate_by_touch: true,
|
||||
states: %{
|
||||
"0" => %{type: 2, next_state: 1, timeout: -1, can_touch: 2},
|
||||
"1" => %{type: 999, next_state: -1, timeout: -1, can_touch: 0}
|
||||
}
|
||||
},
|
||||
%{
|
||||
reactor_id: 200300, # Silver Flower
|
||||
activate_by_touch: true,
|
||||
states: %{
|
||||
"0" => %{type: 2, next_state: 1, timeout: -1, can_touch: 2},
|
||||
"1" => %{type: 999, next_state: -1, timeout: -1, can_touch: 0}
|
||||
}
|
||||
},
|
||||
%{
|
||||
reactor_id: 100011, # Mysterious Herb
|
||||
activate_by_touch: true,
|
||||
states: %{
|
||||
"0" => %{type: 2, next_state: 1, timeout: -1, can_touch: 2},
|
||||
"1" => %{type: 999, next_state: -1, timeout: -1, can_touch: 0}
|
||||
}
|
||||
},
|
||||
%{
|
||||
reactor_id: 200011, # Mysterious Vein
|
||||
activate_by_touch: true,
|
||||
states: %{
|
||||
"0" => %{type: 2, next_state: 1, timeout: -1, can_touch: 2},
|
||||
"1" => %{type: 999, next_state: -1, timeout: -1, can_touch: 0}
|
||||
}
|
||||
}
|
||||
]
|
||||
|
||||
Enum.each(fallback_reactors, fn reactor_data ->
|
||||
stats = ReactorStats.from_json(reactor_data)
|
||||
:ets.insert(@reactor_stats, {reactor_data.reactor_id, stats})
|
||||
end)
|
||||
|
||||
Logger.info("Created #{length(fallback_reactors)} fallback reactor templates")
|
||||
end
|
||||
end
|
||||
Reference in New Issue
Block a user