kimi gone wild
This commit is contained in:
675
lib/odinsea/game/skill_factory.ex
Normal file
675
lib/odinsea/game/skill_factory.ex
Normal file
@@ -0,0 +1,675 @@
|
||||
defmodule Odinsea.Game.SkillFactory do
|
||||
@moduledoc """
|
||||
Skill Factory - loads and caches skill data.
|
||||
|
||||
Ported from Java: client/SkillFactory.java
|
||||
|
||||
This module loads skill metadata from cached JSON files.
|
||||
The JSON files should be exported from the Java server's WZ data providers.
|
||||
|
||||
Skill data is cached in ETS for fast lookups.
|
||||
"""
|
||||
|
||||
use GenServer
|
||||
require Logger
|
||||
|
||||
alias Odinsea.Game.{Skill, StatEffect}
|
||||
|
||||
# ETS table names
|
||||
@skill_cache :odinsea_skill_cache
|
||||
@skill_names :odinsea_skill_names
|
||||
@skills_by_job :odinsea_skills_by_job
|
||||
@summon_skills :odinsea_summon_skills
|
||||
|
||||
# Data file paths (relative to priv directory)
|
||||
@skill_data_file "data/skills.json"
|
||||
@skill_strings_file "data/skill_strings.json"
|
||||
|
||||
defmodule SummonSkillEntry do
|
||||
@moduledoc "Summon skill attack data"
|
||||
|
||||
@type t :: %__MODULE__{
|
||||
skill_id: integer(),
|
||||
type: integer(),
|
||||
mob_count: integer(),
|
||||
attack_count: integer(),
|
||||
lt: {integer(), integer()},
|
||||
rb: {integer(), integer()},
|
||||
delay: integer()
|
||||
}
|
||||
|
||||
defstruct [
|
||||
:skill_id,
|
||||
:type,
|
||||
:mob_count,
|
||||
:attack_count,
|
||||
:lt,
|
||||
:rb,
|
||||
:delay
|
||||
]
|
||||
end
|
||||
|
||||
## Public API
|
||||
|
||||
@doc "Starts the SkillFactory GenServer"
|
||||
def start_link(opts \\ []) do
|
||||
GenServer.start_link(__MODULE__, opts, name: __MODULE__)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Gets a skill by ID.
|
||||
Returns nil if not found.
|
||||
"""
|
||||
@spec get_skill(integer()) :: Skill.t() | nil
|
||||
def get_skill(skill_id) do
|
||||
case :ets.lookup(@skill_cache, skill_id) do
|
||||
[{^skill_id, skill}] -> skill
|
||||
[] -> nil
|
||||
end
|
||||
end
|
||||
|
||||
@doc """
|
||||
Gets skill name by ID.
|
||||
"""
|
||||
@spec get_skill_name(integer()) :: String.t()
|
||||
def get_skill_name(skill_id) do
|
||||
case :ets.lookup(@skill_names, skill_id) do
|
||||
[{^skill_id, name}] -> name
|
||||
[] -> "UNKNOWN"
|
||||
end
|
||||
end
|
||||
|
||||
@doc """
|
||||
Gets all skills for a specific job.
|
||||
"""
|
||||
@spec get_skills_by_job(integer()) :: [integer()]
|
||||
def get_skills_by_job(job_id) do
|
||||
case :ets.lookup(@skills_by_job, job_id) do
|
||||
[{^job_id, skills}] -> skills
|
||||
[] -> []
|
||||
end
|
||||
end
|
||||
|
||||
@doc """
|
||||
Gets summon skill entry for a skill ID.
|
||||
"""
|
||||
@spec get_summon_data(integer()) :: SummonSkillEntry.t() | nil
|
||||
def get_summon_data(skill_id) do
|
||||
case :ets.lookup(@summon_skills, skill_id) do
|
||||
[{^skill_id, entry}] -> entry
|
||||
[] -> nil
|
||||
end
|
||||
end
|
||||
|
||||
@doc """
|
||||
Checks if a skill exists.
|
||||
"""
|
||||
@spec skill_exists?(integer()) :: boolean()
|
||||
def skill_exists?(skill_id) do
|
||||
:ets.member(@skill_cache, skill_id)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Gets all loaded skill IDs.
|
||||
"""
|
||||
@spec get_all_skill_ids() :: [integer()]
|
||||
def get_all_skill_ids do
|
||||
:ets.select(@skill_cache, [{{:"$1", :_}, [], [:"$1"]}])
|
||||
end
|
||||
|
||||
@doc """
|
||||
Gets skill effect for a specific level.
|
||||
Convenience function that combines get_skill and Skill.get_effect.
|
||||
"""
|
||||
@spec get_effect(integer(), integer()) :: StatEffect.t() | nil
|
||||
def get_effect(skill_id, level) do
|
||||
case get_skill(skill_id) do
|
||||
nil -> nil
|
||||
skill -> Skill.get_effect(skill, level)
|
||||
end
|
||||
end
|
||||
|
||||
@doc """
|
||||
Checks if a skill is a beginner skill.
|
||||
"""
|
||||
@spec is_beginner_skill?(integer()) :: boolean()
|
||||
def is_beginner_skill?(skill_id) do
|
||||
job_id = div(skill_id, 10000)
|
||||
job_id in [0, 1000, 2000, 2001, 2002, 3000, 3001, 1]
|
||||
end
|
||||
|
||||
@doc """
|
||||
Gets the job ID for a skill.
|
||||
"""
|
||||
@spec get_skill_job(integer()) :: integer()
|
||||
def get_skill_job(skill_id) do
|
||||
div(skill_id, 10000)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Reloads skill data from files.
|
||||
"""
|
||||
def reload do
|
||||
GenServer.call(__MODULE__, :reload, :infinity)
|
||||
end
|
||||
|
||||
## GenServer Callbacks
|
||||
|
||||
@impl true
|
||||
def init(_opts) do
|
||||
# Create ETS tables
|
||||
:ets.new(@skill_cache, [:set, :public, :named_table, read_concurrency: true])
|
||||
:ets.new(@skill_names, [:set, :public, :named_table, read_concurrency: true])
|
||||
:ets.new(@skills_by_job, [:set, :public, :named_table, read_concurrency: true])
|
||||
:ets.new(@summon_skills, [:set, :public, :named_table, read_concurrency: true])
|
||||
|
||||
# Load data
|
||||
load_skill_data()
|
||||
|
||||
{:ok, %{}}
|
||||
end
|
||||
|
||||
@impl true
|
||||
def handle_call(:reload, _from, state) do
|
||||
Logger.info("Reloading skill data...")
|
||||
load_skill_data()
|
||||
{:reply, :ok, state}
|
||||
end
|
||||
|
||||
## Private Functions
|
||||
|
||||
defp load_skill_data do
|
||||
priv_dir = :code.priv_dir(:odinsea) |> to_string()
|
||||
|
||||
# Try to load from JSON files
|
||||
load_skill_strings(Path.join(priv_dir, @skill_strings_file))
|
||||
load_skills(Path.join(priv_dir, @skill_data_file))
|
||||
|
||||
skill_count = :ets.info(@skill_cache, :size)
|
||||
Logger.info("Loaded #{skill_count} skills")
|
||||
end
|
||||
|
||||
defp load_skill_strings(file_path) do
|
||||
case File.read(file_path) do
|
||||
{:ok, content} ->
|
||||
case Jason.decode(content) do
|
||||
{:ok, data} when is_map(data) ->
|
||||
Enum.each(data, fn {id_str, name} ->
|
||||
case Integer.parse(id_str) do
|
||||
{skill_id, ""} -> :ets.insert(@skill_names, {skill_id, name})
|
||||
_ -> :ok
|
||||
end
|
||||
end)
|
||||
|
||||
{:error, reason} ->
|
||||
Logger.warn("Failed to parse skill strings JSON: #{inspect(reason)}")
|
||||
create_fallback_strings()
|
||||
end
|
||||
|
||||
{:error, :enoent} ->
|
||||
Logger.warn("Skill strings file not found: #{file_path}, using fallback data")
|
||||
create_fallback_strings()
|
||||
|
||||
{:error, reason} ->
|
||||
Logger.error("Failed to read skill strings: #{inspect(reason)}")
|
||||
create_fallback_strings()
|
||||
end
|
||||
end
|
||||
|
||||
defp load_skills(file_path) do
|
||||
case File.read(file_path) do
|
||||
{:ok, content} ->
|
||||
case Jason.decode(content, keys: :atoms) do
|
||||
{:ok, skills} when is_list(skills) ->
|
||||
Enum.each(skills, fn skill_data ->
|
||||
skill = build_skill(skill_data)
|
||||
:ets.insert(@skill_cache, {skill.id, skill})
|
||||
|
||||
# Index by job
|
||||
job_id = div(skill.id, 10000)
|
||||
|
||||
existing =
|
||||
case :ets.lookup(@skills_by_job, job_id) do
|
||||
[{^job_id, list}] -> list
|
||||
[] -> []
|
||||
end
|
||||
|
||||
:ets.insert(@skills_by_job, {job_id, [skill.id | existing]})
|
||||
|
||||
# Check for summon data
|
||||
if skill_data[:summon] do
|
||||
entry = build_summon_entry(skill.id, skill_data[:summon])
|
||||
:ets.insert(@summon_skills, {skill.id, entry})
|
||||
end
|
||||
end)
|
||||
|
||||
{:error, reason} ->
|
||||
Logger.warn("Failed to parse skills JSON: #{inspect(reason)}")
|
||||
create_fallback_skills()
|
||||
end
|
||||
|
||||
{:error, :enoent} ->
|
||||
Logger.warn("Skills file not found: #{file_path}, using fallback data")
|
||||
create_fallback_skills()
|
||||
|
||||
{:error, reason} ->
|
||||
Logger.error("Failed to read skills: #{inspect(reason)}")
|
||||
create_fallback_skills()
|
||||
end
|
||||
end
|
||||
|
||||
defp build_skill(data) do
|
||||
effects =
|
||||
(data[:effects] || [])
|
||||
|> Enum.map(&build_stat_effect/1)
|
||||
|
||||
pvp_effects =
|
||||
if data[:pvp_effects] do
|
||||
Enum.map(data[:pvp_effects], &build_stat_effect/1)
|
||||
else
|
||||
nil
|
||||
end
|
||||
|
||||
%Skill{
|
||||
id: data[:id] || data[:skill_id] || 0,
|
||||
name: data[:name] || "",
|
||||
element: parse_element(data[:element]),
|
||||
max_level: data[:max_level] || 0,
|
||||
true_max: data[:true_max] || data[:max_level] || 0,
|
||||
master_level: data[:master_level] || 0,
|
||||
effects: effects,
|
||||
pvp_effects: pvp_effects,
|
||||
required_skills: data[:required_skills] || [],
|
||||
skill_type: data[:skill_type] || 0,
|
||||
animation: data[:animation],
|
||||
animation_time: data[:animation_time] || 0,
|
||||
delay: data[:delay] || 0,
|
||||
invisible: data[:invisible] || false,
|
||||
time_limited: data[:time_limited] || false,
|
||||
combat_orders: data[:combat_orders] || false,
|
||||
charge_skill: data[:charge_skill] || false,
|
||||
magic: data[:magic] || false,
|
||||
caster_move: data[:caster_move] || false,
|
||||
push_target: data[:push_target] || false,
|
||||
pull_target: data[:pull_target] || false,
|
||||
not_removed: data[:not_removed] || false,
|
||||
pvp_disabled: data[:pvp_disabled] || false,
|
||||
event_taming_mob: data[:event_taming_mob] || 0
|
||||
}
|
||||
end
|
||||
|
||||
defp build_stat_effect(data) do
|
||||
%StatEffect{
|
||||
source_id: data[:source_id] || 0,
|
||||
level: data[:level] || 1,
|
||||
is_skill: data[:is_skill] || true,
|
||||
duration: data[:duration] || -1,
|
||||
over_time: data[:over_time] || false,
|
||||
hp: data[:hp] || 0,
|
||||
mp: data[:mp] || 0,
|
||||
hp_r: data[:hp_r] || 0.0,
|
||||
mp_r: data[:mp_r] || 0.0,
|
||||
mhp_r: data[:mhp_r] || 0,
|
||||
mmp_r: data[:mmp_r] || 0,
|
||||
watk: data[:watk] || data[:pad] || 0,
|
||||
wdef: data[:wdef] || data[:pdd] || 0,
|
||||
matk: data[:matk] || data[:mad] || 0,
|
||||
mdef: data[:mdef] || data[:mdd] || 0,
|
||||
acc: data[:acc] || 0,
|
||||
avoid: data[:avoid] || data[:eva] || 0,
|
||||
hands: data[:hands] || 0,
|
||||
speed: data[:speed] || 0,
|
||||
jump: data[:jump] || 0,
|
||||
mastery: data[:mastery] || 0,
|
||||
damage: data[:damage] || 100,
|
||||
pdd_r: data[:pdd_r] || 0,
|
||||
mdd_r: data[:mdd_r] || 0,
|
||||
dam_r: data[:dam_r] || 0,
|
||||
bd_r: data[:bd_r] || 0,
|
||||
ignore_mob: data[:ignore_mob] || 0,
|
||||
critical_damage_min: data[:critical_damage_min] || 0,
|
||||
critical_damage_max: data[:critical_damage_max] || 0,
|
||||
asr_r: data[:asr_r] || 0,
|
||||
er: data[:er] || 0,
|
||||
prop: data[:prop] || 100,
|
||||
mob_count: data[:mob_count] || 1,
|
||||
attack_count: data[:attack_count] || 1,
|
||||
bullet_count: data[:bullet_count] || 1,
|
||||
cooldown: data[:cooldown] || data[:cooltime] || 0,
|
||||
interval: data[:interval] || 0,
|
||||
mp_con: data[:mp_con] || 0,
|
||||
hp_con: data[:hp_con] || 0,
|
||||
force_con: data[:force_con] || 0,
|
||||
mp_con_reduce: data[:mp_con_reduce] || 0,
|
||||
move_to: data[:move_to] || -1,
|
||||
morph_id: data[:morph] || data[:morph_id] || 0,
|
||||
summon_movement_type: parse_summon_movement(data[:summon_movement]),
|
||||
dot: data[:dot] || 0,
|
||||
dot_time: data[:dot_time] || 0,
|
||||
thaw: data[:thaw] || 0,
|
||||
self_destruction: data[:self_destruction] || 0,
|
||||
pvp_damage: data[:pvp_damage] || 0,
|
||||
inc_pvp_damage: data[:inc_pvp_damage] || 0,
|
||||
indie_pad: data[:indie_pad] || 0,
|
||||
indie_mad: data[:indie_mad] || 0,
|
||||
indie_mhp: data[:indie_mhp] || 0,
|
||||
indie_mmp: data[:indie_mmp] || 0,
|
||||
indie_speed: data[:indie_speed] || 0,
|
||||
indie_jump: data[:indie_jump] || 0,
|
||||
indie_acc: data[:indie_acc] || 0,
|
||||
indie_eva: data[:indie_eva] || 0,
|
||||
indie_pdd: data[:indie_pdd] || 0,
|
||||
indie_mdd: data[:indie_mdd] || 0,
|
||||
indie_all_stat: data[:indie_all_stat] || 0,
|
||||
str: data[:str] || 0,
|
||||
dex: data[:dex] || 0,
|
||||
int: data[:int] || 0,
|
||||
luk: data[:luk] || 0,
|
||||
str_x: data[:str_x] || 0,
|
||||
dex_x: data[:dex_x] || 0,
|
||||
int_x: data[:int_x] || 0,
|
||||
luk_x: data[:luk_x] || 0,
|
||||
x: data[:x] || 0,
|
||||
y: data[:y] || 0,
|
||||
z: data[:z] || 0,
|
||||
stat_ups: data[:stat_ups] || %{},
|
||||
monster_status: data[:monster_status] || %{}
|
||||
}
|
||||
end
|
||||
|
||||
defp build_summon_entry(skill_id, summon_data) do
|
||||
%SummonSkillEntry{
|
||||
skill_id: skill_id,
|
||||
type: summon_data[:type] || 0,
|
||||
mob_count: summon_data[:mob_count] || 1,
|
||||
attack_count: summon_data[:attack_count] || 1,
|
||||
lt: parse_point(summon_data[:lt]) || {-100, -100},
|
||||
rb: parse_point(summon_data[:rb]) || {100, 100},
|
||||
delay: summon_data[:delay] || 0
|
||||
}
|
||||
end
|
||||
|
||||
defp parse_element(nil), do: :neutral
|
||||
defp parse_element("f"), do: :fire
|
||||
defp parse_element("i"), do: :ice
|
||||
defp parse_element("l"), do: :lightning
|
||||
defp parse_element("p"), do: :poison
|
||||
defp parse_element("h"), do: :holy
|
||||
defp parse_element("d"), do: :dark
|
||||
defp parse_element("s"), do: :physical
|
||||
defp parse_element(atom) when is_atom(atom), do: atom
|
||||
defp parse_element(_), do: :neutral
|
||||
|
||||
defp parse_summon_movement(nil), do: nil
|
||||
defp parse_summon_movement("follow"), do: :follow
|
||||
defp parse_summon_movement("stationary"), do: :stationary
|
||||
defp parse_summon_movement("circle_follow"), do: :circle_follow
|
||||
defp parse_summon_movement(atom) when is_atom(atom), do: atom
|
||||
defp parse_summon_movement(_), do: nil
|
||||
|
||||
defp parse_point(nil), do: nil
|
||||
defp parse_point({x, y}), do: {x, y}
|
||||
defp parse_point([x, y]), do: {x, y}
|
||||
defp parse_point(%{x: x, y: y}), do: {x, y}
|
||||
defp parse_point(_), do: nil
|
||||
|
||||
# Fallback data for basic testing without WZ exports
|
||||
defp create_fallback_strings do
|
||||
fallback_names = %{
|
||||
# Beginner skills
|
||||
1_000 => "Three Snails",
|
||||
1_001 => "Recovery",
|
||||
1_002 => "Nimble Feet",
|
||||
1_003 => "Monster Rider",
|
||||
1_004 => "Echo of Hero",
|
||||
|
||||
# Warrior 1st job
|
||||
100_000 => "Power Strike",
|
||||
100_001 => "Slash Blast",
|
||||
100_002 => "Iron Body",
|
||||
100_003 => "Iron Body",
|
||||
100_004 => "Power Strike",
|
||||
100_005 => "Slash Blast",
|
||||
100_006 => "Iron Body",
|
||||
100_007 => "Power Strike",
|
||||
100_008 => "Slash Blast",
|
||||
100_009 => "Iron Body",
|
||||
100_010 => "Power Strike",
|
||||
100_100 => "Power Strike",
|
||||
100_101 => "Slash Blast",
|
||||
100_102 => "Iron Body",
|
||||
|
||||
# Magician 1st job
|
||||
200_000 => "Magic Claw",
|
||||
200_001 => "Teleport",
|
||||
200_002 => "Magic Guard",
|
||||
200_003 => "Magic Armor",
|
||||
200_004 => "Energy Bolt",
|
||||
200_005 => "Magic Claw",
|
||||
200_006 => "Teleport",
|
||||
200_007 => "Magic Guard",
|
||||
200_008 => "Magic Armor",
|
||||
200_009 => "Energy Bolt",
|
||||
200_100 => "Magic Claw",
|
||||
200_101 => "Teleport",
|
||||
200_102 => "Magic Guard",
|
||||
200_103 => "Magic Armor",
|
||||
200_104 => "Energy Bolt",
|
||||
|
||||
# Bowman 1st job
|
||||
300_000 => "Arrow Blow",
|
||||
300_001 => "Double Shot",
|
||||
300_002 => "Critical Shot",
|
||||
300_003 => "The Eye of Amazon",
|
||||
300_004 => "Focus",
|
||||
300_100 => "Arrow Blow",
|
||||
300_101 => "Double Shot",
|
||||
300_102 => "Critical Shot",
|
||||
300_103 => "The Eye of Amazon",
|
||||
300_104 => "Focus",
|
||||
|
||||
# Thief 1st job
|
||||
400_000 => "Lucky Seven",
|
||||
400_001 => "Double Stab",
|
||||
400_002 => "Disorder",
|
||||
400_003 => "Dark Sight",
|
||||
400_004 => "Lucky Seven",
|
||||
400_005 => "Double Stab",
|
||||
400_100 => "Lucky Seven",
|
||||
400_101 => "Double Stab",
|
||||
400_102 => "Disorder",
|
||||
400_103 => "Dark Sight",
|
||||
400_104 => "Lucky Seven",
|
||||
400_105 => "Double Stab",
|
||||
|
||||
# Pirate 1st job
|
||||
500_000 => "Somersault Kick",
|
||||
500_001 => "Double Fire",
|
||||
500_002 => "Dash",
|
||||
500_003 => "Shadow Heart",
|
||||
500_004 => "Somersault Kick",
|
||||
500_005 => "Double Fire",
|
||||
500_100 => "Somersault Kick",
|
||||
500_101 => "Double Fire",
|
||||
500_102 => "Dash",
|
||||
500_103 => "Shadow Heart",
|
||||
500_104 => "Somersault Kick",
|
||||
500_105 => "Double Fire",
|
||||
|
||||
# GM skills
|
||||
9_001_000 => "Haste",
|
||||
9_001_001 => "Dragon Roar",
|
||||
9_001_002 => "Holy Symbol",
|
||||
9_001_003 => "Heal",
|
||||
9_001_004 => "Hide",
|
||||
9_001_005 => "Resurrection",
|
||||
9_001_006 => "Hyper Body",
|
||||
9_001_007 => "Holy Shield",
|
||||
9_001_008 => "Holy Shield",
|
||||
|
||||
# 4th job common
|
||||
1_122_004 => "Hero's Will",
|
||||
1_222_004 => "Hero's Will",
|
||||
1_322_004 => "Hero's Will",
|
||||
2_122_004 => "Hero's Will",
|
||||
2_222_004 => "Hero's Will",
|
||||
2_322_004 => "Hero's Will",
|
||||
3_122_004 => "Hero's Will",
|
||||
4_122_004 => "Hero's Will",
|
||||
4_222_004 => "Hero's Will",
|
||||
5_122_004 => "Hero's Will",
|
||||
5_222_004 => "Hero's Will",
|
||||
|
||||
# Maple Warrior (all 4th jobs)
|
||||
1_121_000 => "Maple Warrior",
|
||||
1_221_000 => "Maple Warrior",
|
||||
1_321_000 => "Maple Warrior",
|
||||
2_121_000 => "Maple Warrior",
|
||||
2_221_000 => "Maple Warrior",
|
||||
2_321_000 => "Maple Warrior",
|
||||
3_121_000 => "Maple Warrior",
|
||||
3_221_000 => "Maple Warrior",
|
||||
4_121_000 => "Maple Warrior",
|
||||
4_221_000 => "Maple Warrior",
|
||||
5_121_000 => "Maple Warrior",
|
||||
5_221_000 => "Maple Warrior"
|
||||
}
|
||||
|
||||
Enum.each(fallback_names, fn {skill_id, name} ->
|
||||
:ets.insert(@skill_names, {skill_id, name})
|
||||
end)
|
||||
end
|
||||
|
||||
defp create_fallback_skills do
|
||||
# Create some basic beginner skills as fallback
|
||||
fallback_skills = [
|
||||
%{
|
||||
id: 1_000,
|
||||
name: "Three Snails",
|
||||
element: :physical,
|
||||
max_level: 3,
|
||||
true_max: 3,
|
||||
effects: [
|
||||
%{level: 1, damage: 150, mp_con: 10, mob_count: 1, x: 15},
|
||||
%{level: 2, damage: 200, mp_con: 15, mob_count: 1, x: 30},
|
||||
%{level: 3, damage: 250, mp_con: 20, mob_count: 1, x: 45}
|
||||
]
|
||||
},
|
||||
%{
|
||||
id: 1_001,
|
||||
name: "Recovery",
|
||||
element: :neutral,
|
||||
max_level: 3,
|
||||
true_max: 3,
|
||||
effects: [
|
||||
%{level: 1, duration: 30000, hp: 10, interval: 2000, x: 10},
|
||||
%{level: 2, duration: 30000, hp: 20, interval: 1900, x: 20},
|
||||
%{level: 3, duration: 30000, hp: 30, interval: 1800, x: 30}
|
||||
]
|
||||
},
|
||||
%{
|
||||
id: 1_002,
|
||||
name: "Nimble Feet",
|
||||
element: :neutral,
|
||||
max_level: 3,
|
||||
true_max: 3,
|
||||
effects: [
|
||||
%{level: 1, duration: 4000, speed: 10, x: 10},
|
||||
%{level: 2, duration: 8000, speed: 15, x: 15},
|
||||
%{level: 3, duration: 12000, speed: 20, x: 20}
|
||||
]
|
||||
},
|
||||
%{
|
||||
id: 1_004,
|
||||
name: "Echo of Hero",
|
||||
element: :neutral,
|
||||
max_level: 1,
|
||||
true_max: 1,
|
||||
effects: [
|
||||
%{level: 1, duration: 1200000, watk: 4, wdef: 4, matk: 4, mdef: 4, x: 4}
|
||||
]
|
||||
},
|
||||
%{
|
||||
id: 100_000,
|
||||
name: "Power Strike",
|
||||
element: :physical,
|
||||
max_level: 20,
|
||||
true_max: 20,
|
||||
skill_type: 1,
|
||||
effects: [
|
||||
%{level: 1, damage: 145, mp_con: 8, mob_count: 1, attack_count: 1},
|
||||
%{level: 10, damage: 190, mp_con: 16, mob_count: 1, attack_count: 1},
|
||||
%{level: 20, damage: 245, mp_con: 24, mob_count: 1, attack_count: 1}
|
||||
]
|
||||
},
|
||||
%{
|
||||
id: 100_001,
|
||||
name: "Slash Blast",
|
||||
element: :physical,
|
||||
max_level: 20,
|
||||
true_max: 20,
|
||||
skill_type: 1,
|
||||
effects: [
|
||||
%{level: 1, damage: 85, mp_con: 8, mob_count: 3, attack_count: 1},
|
||||
%{level: 10, damage: 115, mp_con: 16, mob_count: 4, attack_count: 1},
|
||||
%{level: 20, damage: 150, mp_con: 24, mob_count: 6, attack_count: 1}
|
||||
]
|
||||
},
|
||||
%{
|
||||
id: 200_000,
|
||||
name: "Magic Claw",
|
||||
element: :neutral,
|
||||
max_level: 20,
|
||||
true_max: 20,
|
||||
magic: true,
|
||||
skill_type: 1,
|
||||
effects: [
|
||||
%{level: 1, damage: 132, mp_con: 12, mob_count: 2, attack_count: 1, x: 22},
|
||||
%{level: 10, damage: 156, mp_con: 24, mob_count: 2, attack_count: 1, x: 26},
|
||||
%{level: 20, damage: 182, mp_con: 36, mob_count: 2, attack_count: 1, x: 30}
|
||||
]
|
||||
},
|
||||
%{
|
||||
id: 200_001,
|
||||
name: "Teleport",
|
||||
element: :neutral,
|
||||
max_level: 20,
|
||||
true_max: 20,
|
||||
skill_type: 2,
|
||||
effects: [
|
||||
%{level: 1, mp_con: 40, x: 70},
|
||||
%{level: 10, mp_con: 35, x: 115},
|
||||
%{level: 20, mp_con: 30, x: 160}
|
||||
]
|
||||
},
|
||||
%{
|
||||
id: 200_002,
|
||||
name: "Magic Guard",
|
||||
element: :neutral,
|
||||
max_level: 20,
|
||||
true_max: 20,
|
||||
skill_type: 2,
|
||||
effects: [
|
||||
%{level: 1, x: 15},
|
||||
%{level: 10, x: 42},
|
||||
%{level: 20, x: 70}
|
||||
]
|
||||
}
|
||||
]
|
||||
|
||||
Enum.each(fallback_skills, fn skill_data ->
|
||||
skill = build_skill(skill_data)
|
||||
:ets.insert(@skill_cache, {skill.id, skill})
|
||||
|
||||
job_id = div(skill.id, 10000)
|
||||
|
||||
existing =
|
||||
case :ets.lookup(@skills_by_job, job_id) do
|
||||
[{^job_id, list}] -> list
|
||||
[] -> []
|
||||
end
|
||||
|
||||
:ets.insert(@skills_by_job, {job_id, [skill.id | existing]})
|
||||
end)
|
||||
end
|
||||
end
|
||||
Reference in New Issue
Block a user