1062 lines
33 KiB
Elixir
1062 lines
33 KiB
Elixir
defmodule Odinsea.World.Family do
|
|
@moduledoc """
|
|
Family management service.
|
|
Ported from src/handling/world/family/MapleFamily.java
|
|
|
|
Manages family trees with senior/junior relationships.
|
|
Supports family blessings, reputation, and pedigree tracking.
|
|
"""
|
|
|
|
use GenServer
|
|
|
|
require Logger
|
|
|
|
alias Odinsea.Database.Repo
|
|
import Ecto.Query
|
|
|
|
# ============================================================================
|
|
# Data Structures
|
|
# ============================================================================
|
|
|
|
defmodule FamilyCharacter do
|
|
@moduledoc "Family member representation"
|
|
defstruct [
|
|
:id, :name, :level, :job, :channel,
|
|
:senior_id, :junior1_id, :junior2_id,
|
|
:current_rep, :total_rep,
|
|
:online, :pedigree, :descendants
|
|
]
|
|
end
|
|
|
|
# ============================================================================
|
|
# Client API
|
|
# ============================================================================
|
|
|
|
def start_link(_) do
|
|
GenServer.start_link(__MODULE__, [], name: __MODULE__)
|
|
end
|
|
|
|
@doc """
|
|
Creates a new family with the given leader.
|
|
Returns {:ok, family_id} on success, {:error, reason} on failure.
|
|
"""
|
|
def create_family(leader_id) do
|
|
GenServer.call(__MODULE__, {:create_family, leader_id})
|
|
end
|
|
|
|
@doc """
|
|
Gets a family by ID.
|
|
"""
|
|
def get_family(family_id) do
|
|
GenServer.call(__MODULE__, {:get_family, family_id})
|
|
end
|
|
|
|
@doc """
|
|
Gets family by character ID.
|
|
"""
|
|
def get_family_by_character(character_id) do
|
|
GenServer.call(__MODULE__, {:get_family_by_character, character_id})
|
|
end
|
|
|
|
@doc """
|
|
Adds a family member (junior to a senior).
|
|
"""
|
|
def add_junior(family_id, senior_id, junior_character) do
|
|
GenServer.call(__MODULE__, {:add_junior, family_id, senior_id, junior_character})
|
|
end
|
|
|
|
@doc """
|
|
Removes a junior relationship.
|
|
"""
|
|
def remove_junior(family_id, senior_id, junior_id) do
|
|
GenServer.call(__MODULE__, {:remove_junior, family_id, senior_id, junior_id})
|
|
end
|
|
|
|
@doc """
|
|
Removes a senior relationship (character becomes leader of new family).
|
|
"""
|
|
def remove_senior(family_id, character_id) do
|
|
GenServer.call(__MODULE__, {:remove_senior, family_id, character_id})
|
|
end
|
|
|
|
@doc """
|
|
Leaves family completely.
|
|
"""
|
|
def leave_family(family_id, character_id) do
|
|
GenServer.call(__MODULE__, {:leave_family, family_id, character_id})
|
|
end
|
|
|
|
@doc """
|
|
Sets family notice.
|
|
"""
|
|
def set_notice(family_id, notice, leader_id) do
|
|
GenServer.call(__MODULE__, {:set_notice, family_id, notice, leader_id})
|
|
end
|
|
|
|
@doc """
|
|
Sets member online status.
|
|
"""
|
|
def set_online(family_id, character_id, online, channel) do
|
|
GenServer.call(__MODULE__, {:set_online, family_id, character_id, online, channel})
|
|
end
|
|
|
|
@doc """
|
|
Updates member info.
|
|
"""
|
|
def update_member(family_id, character) do
|
|
GenServer.call(__MODULE__, {:update_member, family_id, character})
|
|
end
|
|
|
|
@doc """
|
|
Gains reputation for a member.
|
|
"""
|
|
def gain_rep(family_id, character_id, amount, senior_old_level, senior_name) do
|
|
GenServer.call(__MODULE__, {:gain_rep, family_id, character_id, amount, senior_old_level, senior_name})
|
|
end
|
|
|
|
@doc """
|
|
Merges two families (old into new).
|
|
Called when a character with juniors joins as a junior.
|
|
"""
|
|
def merge_families(new_family_id, old_family_id) do
|
|
GenServer.call(__MODULE__, {:merge_families, new_family_id, old_family_id})
|
|
end
|
|
|
|
@doc """
|
|
Disbands a family.
|
|
"""
|
|
def disband_family(family_id) do
|
|
GenServer.call(__MODULE__, {:disband_family, family_id})
|
|
end
|
|
|
|
@doc """
|
|
Broadcasts to family members.
|
|
"""
|
|
def broadcast(family_id, packet, recipient_ids \\ nil) do
|
|
GenServer.cast(__MODULE__, {:broadcast, family_id, packet, recipient_ids})
|
|
end
|
|
|
|
@doc """
|
|
Gets pedigree for a character (all related family members).
|
|
"""
|
|
def get_pedigree(family_id, character_id) do
|
|
GenServer.call(__MODULE__, {:get_pedigree, family_id, character_id})
|
|
end
|
|
|
|
@doc """
|
|
Gets all juniors recursively.
|
|
"""
|
|
def get_all_juniors(family_id, character_id) do
|
|
GenServer.call(__MODULE__, {:get_all_juniors, family_id, character_id})
|
|
end
|
|
|
|
@doc """
|
|
Gets online juniors (self + direct juniors + their juniors).
|
|
"""
|
|
def get_online_juniors(family_id, character_id) do
|
|
GenServer.call(__MODULE__, {:get_online_juniors, family_id, character_id})
|
|
end
|
|
|
|
# ============================================================================
|
|
# Server Callbacks
|
|
# ============================================================================
|
|
|
|
@impl true
|
|
def init(_) do
|
|
# Load families from database
|
|
families = load_families_from_db()
|
|
|
|
Logger.info("Family service initialized with #{map_size(families)} families")
|
|
{:ok, %{families: families}}
|
|
end
|
|
|
|
@impl true
|
|
def handle_call({:create_family, leader_id}, _from, state) do
|
|
case create_family_in_db(leader_id) do
|
|
{:ok, family_id} ->
|
|
family = %{
|
|
id: family_id,
|
|
leader_id: leader_id,
|
|
leader_name: nil, # Will be set when first member is added
|
|
notice: "",
|
|
members: %{}
|
|
}
|
|
|
|
new_state = %{state | families: Map.put(state.families, family_id, family)}
|
|
Logger.info("Family #{family_id} created with leader #{leader_id}")
|
|
{:reply, {:ok, family_id}, new_state}
|
|
|
|
{:error, reason} ->
|
|
{:reply, {:error, reason}, state}
|
|
end
|
|
end
|
|
|
|
@impl true
|
|
def handle_call({:get_family, family_id}, _from, state) do
|
|
{:reply, Map.get(state.families, family_id), state}
|
|
end
|
|
|
|
@impl true
|
|
def handle_call({:get_family_by_character, character_id}, _from, state) do
|
|
family = state.families
|
|
|> Map.values()
|
|
|> Enum.find(fn f -> Map.has_key?(f.members, character_id) end)
|
|
|
|
{:reply, family, state}
|
|
end
|
|
|
|
@impl true
|
|
def handle_call({:add_junior, family_id, senior_id, junior}, _from, state) do
|
|
case Map.get(state.families, family_id) do
|
|
nil ->
|
|
{:reply, {:error, :family_not_found}, state}
|
|
|
|
family ->
|
|
senior = Map.get(family.members, senior_id)
|
|
|
|
cond do
|
|
not senior ->
|
|
{:reply, {:error, :senior_not_found}, state}
|
|
|
|
senior.junior1_id != 0 && senior.junior2_id != 0 ->
|
|
{:reply, {:error, :senior_has_max_juniors}, state}
|
|
|
|
Map.has_key?(family.members, junior.id) ->
|
|
{:reply, {:error, :already_in_family}, state}
|
|
|
|
true ->
|
|
# Create junior character
|
|
junior_char = %FamilyCharacter{
|
|
id: junior.id,
|
|
name: junior.name,
|
|
level: junior.level,
|
|
job: junior.job,
|
|
channel: junior.channel_id || 1,
|
|
senior_id: senior_id,
|
|
junior1_id: 0,
|
|
junior2_id: 0,
|
|
current_rep: junior.current_rep || 0,
|
|
total_rep: junior.total_rep || 0,
|
|
online: true,
|
|
pedigree: [],
|
|
descendants: 0
|
|
}
|
|
|
|
# Update senior's junior slot
|
|
updated_senior = if senior.junior1_id == 0 do
|
|
%{senior | junior1_id: junior.id}
|
|
else
|
|
%{senior | junior2_id: junior.id}
|
|
end
|
|
|
|
members = family.members
|
|
|> Map.put(senior_id, updated_senior)
|
|
|> Map.put(junior.id, junior_char)
|
|
|
|
# Check if this is the first member (leader)
|
|
leader_name = if map_size(family.members) == 0 do
|
|
junior.name
|
|
else
|
|
family.leader_name
|
|
end
|
|
|
|
updated_family = %{family |
|
|
members: members,
|
|
leader_name: leader_name || family.leader_name
|
|
}
|
|
|
|
# Recalculate pedigree for affected members
|
|
updated_family = recalculate_pedigrees(updated_family)
|
|
|
|
# Save to database
|
|
save_family_member_to_db(family_id, junior_char)
|
|
update_member_in_db(senior_id, updated_senior)
|
|
|
|
# Broadcast
|
|
broadcast_family_joined(updated_family, junior)
|
|
|
|
{:reply, {:ok, junior_char}, %{state | families: Map.put(state.families, family_id, updated_family)}}
|
|
end
|
|
end
|
|
end
|
|
|
|
@impl true
|
|
def handle_call({:remove_junior, family_id, senior_id, junior_id}, _from, state) do
|
|
case Map.get(state.families, family_id) do
|
|
nil ->
|
|
{:reply, {:error, :family_not_found}, state}
|
|
|
|
family ->
|
|
senior = Map.get(family.members, senior_id)
|
|
junior = Map.get(family.members, junior_id)
|
|
|
|
cond do
|
|
not senior ->
|
|
{:reply, {:error, :senior_not_found}, state}
|
|
|
|
senior.junior1_id != junior_id && senior.junior2_id != junior_id ->
|
|
{:reply, {:error, :not_junior}, state}
|
|
|
|
true ->
|
|
# Update senior's junior slot
|
|
updated_senior = if senior.junior1_id == junior_id do
|
|
%{senior | junior1_id: 0}
|
|
else
|
|
%{senior | junior2_id: 0}
|
|
end
|
|
|
|
# Junior becomes leader of new family
|
|
# Get all juniors of the removed junior
|
|
juniors_family = get_all_juniors_list(family, junior_id)
|
|
|
|
# Create new family for the split
|
|
{:ok, new_family_id} = create_family_in_db(junior_id)
|
|
|
|
# Move juniors to new family
|
|
{remaining_members, moved_members} =
|
|
Enum.split_with(family.members, fn {id, _} ->
|
|
id in juniors_family || id == junior_id
|
|
end)
|
|
|
|
# Update junior (now leader)
|
|
updated_junior = %{junior | senior_id: 0}
|
|
|
|
new_family_members = Map.new([{junior_id, updated_junior} | moved_members])
|
|
new_family = %{
|
|
id: new_family_id,
|
|
leader_id: junior_id,
|
|
leader_name: junior.name,
|
|
notice: "",
|
|
members: new_family_members
|
|
}
|
|
|
|
# Update old family
|
|
old_family_members = Map.new([{senior_id, updated_senior} | remaining_members])
|
|
updated_family = %{family | members: old_family_members}
|
|
updated_family = recalculate_pedigrees(updated_family)
|
|
|
|
# Save to database
|
|
update_member_in_db(senior_id, updated_senior)
|
|
move_members_to_new_family(junior_id, new_family_id, moved_members)
|
|
|
|
# Check if old family should disband (less than 2 members)
|
|
final_state = if map_size(updated_family.members) < 2 do
|
|
disband_family_in_db(family_id)
|
|
broadcast_family_disband(updated_family)
|
|
%{state | families: Map.delete(state.families, family_id)}
|
|
else
|
|
%{state | families: Map.put(state.families, family_id, updated_family)}
|
|
end
|
|
|
|
# Add new family to state
|
|
final_state = %{final_state | families: Map.put(final_state.families, new_family_id, new_family)}
|
|
|
|
{:reply, {:ok, new_family_id}, final_state}
|
|
end
|
|
end
|
|
end
|
|
|
|
@impl true
|
|
def handle_call({:remove_senior, family_id, character_id}, _from, state) do
|
|
case Map.get(state.families, family_id) do
|
|
nil ->
|
|
{:reply, {:error, :family_not_found}, state}
|
|
|
|
family ->
|
|
character = Map.get(family.members, character_id)
|
|
|
|
if not character || character.senior_id == 0 do
|
|
{:reply, {:error, :no_senior}, state}
|
|
else
|
|
senior = Map.get(family.members, character.senior_id)
|
|
|
|
# Update senior's junior slot
|
|
updated_senior = if senior.junior1_id == character_id do
|
|
%{senior | junior1_id: 0}
|
|
else
|
|
%{senior | junior2_id: 0}
|
|
end
|
|
|
|
# Character becomes leader of new family
|
|
{:ok, new_family_id} = create_family_in_db(character_id)
|
|
|
|
# Get character's juniors
|
|
juniors_family = get_all_juniors_list(family, character_id)
|
|
|
|
# Move character and juniors to new family
|
|
{remaining_members, moved_members} =
|
|
Enum.split_with(family.members, fn {id, _} ->
|
|
not (id in juniors_family || id == character_id)
|
|
end)
|
|
|
|
# Update character (now leader)
|
|
updated_character = %{character | senior_id: 0}
|
|
|
|
new_family_members = Map.new([{character_id, updated_character} | moved_members])
|
|
new_family = %{
|
|
id: new_family_id,
|
|
leader_id: character_id,
|
|
leader_name: character.name,
|
|
notice: "",
|
|
members: new_family_members
|
|
}
|
|
|
|
# Update old family
|
|
old_family_members = Map.new([{senior.id, updated_senior} | remaining_members])
|
|
updated_family = %{family | members: old_family_members}
|
|
updated_family = recalculate_pedigrees(updated_family)
|
|
|
|
# Save to database
|
|
update_member_in_db(senior.id, updated_senior)
|
|
move_members_to_new_family(character_id, new_family_id, moved_members)
|
|
|
|
# Check if old family should disband
|
|
final_state = if map_size(updated_family.members) < 2 do
|
|
disband_family_in_db(family_id)
|
|
broadcast_family_disband(updated_family)
|
|
%{state | families: Map.delete(state.families, family_id)}
|
|
else
|
|
%{state | families: Map.put(state.families, family_id, updated_family)}
|
|
end
|
|
|
|
# Add new family to state
|
|
final_state = %{final_state | families: Map.put(final_state.families, new_family_id, new_family)}
|
|
|
|
{:reply, {:ok, new_family_id}, final_state}
|
|
end
|
|
end
|
|
end
|
|
|
|
@impl true
|
|
def handle_call({:leave_family, family_id, character_id}, _from, state) do
|
|
case Map.get(state.families, family_id) do
|
|
nil ->
|
|
{:reply, {:error, :family_not_found}, state}
|
|
|
|
family ->
|
|
character = Map.get(family.members, character_id)
|
|
|
|
if not character do
|
|
{:reply, {:error, :not_in_family}, state}
|
|
else
|
|
# If leader leaves, disband the family
|
|
if character_id == family.leader_id do
|
|
# Disband everyone
|
|
disband_family_in_db(family_id)
|
|
broadcast_family_disband(family)
|
|
|
|
{:reply, :ok, %{state | families: Map.delete(state.families, family_id)}}
|
|
else
|
|
# Handle juniors
|
|
members = family.members
|
|
|
|
# Juniors become their own family leaders
|
|
members = if character.junior1_id != 0 do
|
|
junior1 = Map.get(members, character.junior1_id)
|
|
if junior1 do
|
|
updated_junior1 = %{junior1 | senior_id: 0}
|
|
|
|
# Split off junior's branch
|
|
{:ok, new_family_id} = create_family_in_db(character.junior1_id)
|
|
juniors_family = get_all_juniors_list(family, character.junior1_id)
|
|
|
|
move_members_to_new_family(character.junior1_id, new_family_id,
|
|
Enum.map(juniors_family, fn id -> {id, Map.get(members, id)} end))
|
|
|
|
# Remove from current family
|
|
Map.delete(members, character.junior1_id)
|
|
|> Map.put(character.junior1_id, updated_junior1)
|
|
else
|
|
members
|
|
end
|
|
else
|
|
members
|
|
end
|
|
|
|
members = if character.junior2_id != 0 do
|
|
junior2 = Map.get(members, character.junior2_id)
|
|
if junior2 do
|
|
updated_junior2 = %{junior2 | senior_id: 0}
|
|
|
|
{:ok, new_family_id} = create_family_in_db(character.junior2_id)
|
|
juniors_family = get_all_juniors_list(family, character.junior2_id)
|
|
|
|
move_members_to_new_family(character.junior2_id, new_family_id,
|
|
Enum.map(juniors_family, fn id -> {id, Map.get(members, id)} end))
|
|
|
|
Map.delete(members, character.junior2_id)
|
|
|> Map.put(character.junior2_id, updated_junior2)
|
|
else
|
|
members
|
|
end
|
|
else
|
|
members
|
|
end
|
|
|
|
# Update senior
|
|
members = if character.senior_id != 0 do
|
|
senior = Map.get(members, character.senior_id)
|
|
if senior do
|
|
updated_senior = if senior.junior1_id == character_id do
|
|
%{senior | junior1_id: 0}
|
|
else
|
|
%{senior | junior2_id: 0}
|
|
end
|
|
Map.put(members, character.senior_id, updated_senior)
|
|
else
|
|
members
|
|
end
|
|
else
|
|
members
|
|
end
|
|
|
|
# Remove character
|
|
members = Map.delete(members, character_id)
|
|
|
|
# Check if family should disband
|
|
if map_size(members) < 2 do
|
|
disband_family_in_db(family_id)
|
|
broadcast_family_disband(%{family | members: members})
|
|
{:reply, :ok, %{state | families: Map.delete(state.families, family_id)}}
|
|
else
|
|
updated_family = %{family | members: members}
|
|
updated_family = recalculate_pedigrees(updated_family)
|
|
|
|
remove_family_member_from_db(family_id, character_id)
|
|
|
|
{:reply, :ok, %{state | families: Map.put(state.families, family_id, updated_family)}}
|
|
end
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
@impl true
|
|
def handle_call({:set_notice, family_id, notice, leader_id}, _from, state) do
|
|
case Map.get(state.families, family_id) do
|
|
nil ->
|
|
{:reply, {:error, :family_not_found}, state}
|
|
|
|
%{leader_id: actual_leader} when actual_leader != leader_id ->
|
|
{:reply, {:error, :not_leader}, state}
|
|
|
|
family ->
|
|
updated_family = %{family | notice: String.slice(notice, 0, 255)}
|
|
|
|
update_family_notice_in_db(family_id, updated_family.notice)
|
|
|
|
{:reply, :ok, %{state | families: Map.put(state.families, family_id, updated_family)}}
|
|
end
|
|
end
|
|
|
|
@impl true
|
|
def handle_call({:set_online, family_id, character_id, online, channel}, _from, state) do
|
|
case Map.get(state.families, family_id) do
|
|
nil ->
|
|
{:reply, {:error, :family_not_found}, state}
|
|
|
|
family ->
|
|
case Map.get(family.members, character_id) do
|
|
nil ->
|
|
{:reply, {:error, :not_in_family}, state}
|
|
|
|
character ->
|
|
updated_character = %{character |
|
|
online: online,
|
|
channel: if(online, do: channel, else: -1)
|
|
}
|
|
|
|
members = Map.put(family.members, character_id, updated_character)
|
|
updated_family = %{family | members: members}
|
|
|
|
# Broadcast login/logout
|
|
broadcast_member_login(updated_family, character, online)
|
|
|
|
{:reply, :ok, %{state | families: Map.put(state.families, family_id, updated_family)}}
|
|
end
|
|
end
|
|
end
|
|
|
|
@impl true
|
|
def handle_call({:update_member, family_id, character}, _from, state) do
|
|
case Map.get(state.families, family_id) do
|
|
nil ->
|
|
{:reply, {:error, :family_not_found}, state}
|
|
|
|
family ->
|
|
case Map.get(family.members, character.id) do
|
|
nil ->
|
|
{:reply, {:error, :not_in_family}, state}
|
|
|
|
existing ->
|
|
updated_character = %{existing |
|
|
level: character.level || existing.level,
|
|
job: character.job || existing.job,
|
|
channel: character.channel_id || existing.channel
|
|
}
|
|
|
|
members = Map.put(family.members, character.id, updated_character)
|
|
updated_family = %{family | members: members}
|
|
|
|
# Broadcast level/job change
|
|
if existing.level != updated_character.level do
|
|
broadcast_member_levelup(updated_family, updated_character)
|
|
end
|
|
|
|
if existing.job != updated_character.job do
|
|
broadcast_member_jobchange(updated_family, updated_character)
|
|
end
|
|
|
|
{:reply, :ok, %{state | families: Map.put(state.families, family_id, updated_family)}}
|
|
end
|
|
end
|
|
end
|
|
|
|
@impl true
|
|
def handle_call({:gain_rep, family_id, character_id, amount, senior_old_level, senior_name}, _from, state) do
|
|
case Map.get(state.families, family_id) do
|
|
nil ->
|
|
{:reply, {:error, :family_not_found}, state}
|
|
|
|
family ->
|
|
case Map.get(family.members, character_id) do
|
|
nil ->
|
|
{:reply, {:error, :not_in_family}, state}
|
|
|
|
character ->
|
|
# Reduce rep if senior is higher level
|
|
adjusted_amount = if senior_old_level > character.level do
|
|
div(amount, 2)
|
|
else
|
|
amount
|
|
end
|
|
|
|
updated_character = %{character |
|
|
current_rep: character.current_rep + adjusted_amount,
|
|
total_rep: character.total_rep + adjusted_amount
|
|
}
|
|
|
|
members = Map.put(family.members, character_id, updated_character)
|
|
updated_family = %{family | members: members}
|
|
|
|
# Broadcast rep change
|
|
broadcast_rep_change(updated_family, adjusted_amount, senior_name, character_id)
|
|
|
|
{:reply, {:ok, character.senior_id}, %{state | families: Map.put(state.families, family_id, updated_family)}}
|
|
end
|
|
end
|
|
end
|
|
|
|
@impl true
|
|
def handle_call({:merge_families, new_family_id, old_family_id}, _from, state) do
|
|
new_family = Map.get(state.families, new_family_id)
|
|
old_family = Map.get(state.families, old_family_id)
|
|
|
|
if not new_family or not old_family do
|
|
{:reply, {:error, :family_not_found}, state}
|
|
else
|
|
# Merge old family's members into new family
|
|
merged_members = Map.merge(new_family.members, old_family.members)
|
|
|
|
# Update family IDs for all old members
|
|
merged_members = Enum.map(merged_members, fn {id, member} ->
|
|
if Map.has_key?(old_family.members, id) do
|
|
{id, %{member | family_id: new_family_id}}
|
|
else
|
|
{id, member}
|
|
end
|
|
end)
|
|
|> Map.new()
|
|
|
|
updated_family = %{new_family | members: merged_members}
|
|
updated_family = recalculate_pedigrees(updated_family)
|
|
|
|
# Move members in database
|
|
merge_families_in_db(new_family_id, old_family_id, Map.keys(old_family.members))
|
|
|
|
# Disband old family
|
|
disband_family_in_db(old_family_id)
|
|
|
|
new_state = state
|
|
|> put_in([:families, new_family_id], updated_family)
|
|
|> Map.update!(:families, fn families -> Map.delete(families, old_family_id) end)
|
|
|
|
Logger.info("Family #{old_family_id} merged into #{new_family_id}")
|
|
{:reply, :ok, new_state}
|
|
end
|
|
end
|
|
|
|
@impl true
|
|
def handle_call({:disband_family, family_id}, _from, state) do
|
|
case Map.get(state.families, family_id) do
|
|
nil ->
|
|
{:reply, {:error, :family_not_found}, state}
|
|
|
|
family ->
|
|
# Notify all members
|
|
broadcast_family_disband(family)
|
|
|
|
# Clear family from database
|
|
disband_family_in_db(family_id)
|
|
|
|
Logger.info("Family #{family_id} disbanded")
|
|
{:reply, :ok, %{state | families: Map.delete(state.families, family_id)}}
|
|
end
|
|
end
|
|
|
|
@impl true
|
|
def handle_call({:get_pedigree, family_id, character_id}, _from, state) do
|
|
case Map.get(state.families, family_id) do
|
|
nil ->
|
|
{:reply, [], state}
|
|
|
|
family ->
|
|
pedigree = calculate_pedigree(family, character_id)
|
|
{:reply, pedigree, state}
|
|
end
|
|
end
|
|
|
|
@impl true
|
|
def handle_call({:get_all_juniors, family_id, character_id}, _from, state) do
|
|
case Map.get(state.families, family_id) do
|
|
nil ->
|
|
{:reply, [], state}
|
|
|
|
family ->
|
|
juniors = get_all_juniors_list(family, character_id)
|
|
{:reply, juniors, state}
|
|
end
|
|
end
|
|
|
|
@impl true
|
|
def handle_call({:get_online_juniors, family_id, character_id}, _from, state) do
|
|
case Map.get(state.families, family_id) do
|
|
nil ->
|
|
{:reply, [], state}
|
|
|
|
family ->
|
|
character = Map.get(family.members, character_id)
|
|
|
|
if not character do
|
|
{:reply, [], state}
|
|
else
|
|
online = [character_id]
|
|
|
|
# Direct juniors
|
|
online = if character.junior1_id != 0 do
|
|
junior1 = Map.get(family.members, character.junior1_id)
|
|
if junior1 && junior1.online do
|
|
[character.junior1_id | online]
|
|
else
|
|
online
|
|
end
|
|
else
|
|
online
|
|
end
|
|
|
|
online = if character.junior2_id != 0 do
|
|
junior2 = Map.get(family.members, character.junior2_id)
|
|
if junior2 && junior2.online do
|
|
[character.junior2_id | online]
|
|
else
|
|
online
|
|
end
|
|
else
|
|
online
|
|
end
|
|
|
|
# Juniors' juniors
|
|
online = if character.junior1_id != 0 do
|
|
junior1 = Map.get(family.members, character.junior1_id)
|
|
if junior1 do
|
|
junior1_juniors =
|
|
[junior1.junior1_id, junior1.junior2_id]
|
|
|> Enum.filter(&(&1 != 0))
|
|
|> Enum.filter(fn id ->
|
|
m = Map.get(family.members, id)
|
|
m && m.online
|
|
end)
|
|
junior1_juniors ++ online
|
|
else
|
|
online
|
|
end
|
|
else
|
|
online
|
|
end
|
|
|
|
online = if character.junior2_id != 0 do
|
|
junior2 = Map.get(family.members, character.junior2_id)
|
|
if junior2 do
|
|
junior2_juniors =
|
|
[junior2.junior1_id, junior2.junior2_id]
|
|
|> Enum.filter(&(&1 != 0))
|
|
|> Enum.filter(fn id ->
|
|
m = Map.get(family.members, id)
|
|
m && m.online
|
|
end)
|
|
junior2_juniors ++ online
|
|
else
|
|
online
|
|
end
|
|
else
|
|
online
|
|
end
|
|
|
|
{:reply, online, state}
|
|
end
|
|
end
|
|
end
|
|
|
|
@impl true
|
|
def handle_cast({:broadcast, family_id, packet, recipient_ids}, state) do
|
|
case Map.get(state.families, family_id) do
|
|
nil -> :ok
|
|
family ->
|
|
recipients = if recipient_ids do
|
|
Enum.filter(family.members, fn {id, m} -> id in recipient_ids && m.online end)
|
|
else
|
|
Enum.filter(family.members, fn {_, m} -> m.online end)
|
|
end
|
|
|
|
Enum.each(recipients, fn {id, _} ->
|
|
case Registry.lookup(Odinsea.CharacterRegistry, id) do
|
|
[{pid, _}] -> send(pid, {:send_packet, packet})
|
|
[] -> :ok
|
|
end
|
|
end)
|
|
end
|
|
|
|
{:noreply, state}
|
|
end
|
|
|
|
# ============================================================================
|
|
# Helper Functions
|
|
# ============================================================================
|
|
|
|
defp get_all_juniors_list(family, character_id) do
|
|
character = Map.get(family.members, character_id)
|
|
if not character do
|
|
[]
|
|
else
|
|
juniors = [character_id]
|
|
|
|
juniors = if character.junior1_id != 0 do
|
|
juniors ++ get_all_juniors_list(family, character.junior1_id)
|
|
else
|
|
juniors
|
|
end
|
|
|
|
juniors = if character.junior2_id != 0 do
|
|
juniors ++ get_all_juniors_list(family, character.junior2_id)
|
|
else
|
|
juniors
|
|
end
|
|
|
|
juniors
|
|
end
|
|
end
|
|
|
|
defp calculate_pedigree(family, character_id) do
|
|
character = Map.get(family.members, character_id)
|
|
if not character do
|
|
[]
|
|
else
|
|
pedigree = [character_id]
|
|
|
|
# Add senior and senior's relatives
|
|
pedigree = if character.senior_id != 0 do
|
|
senior = Map.get(family.members, character.senior_id)
|
|
if senior do
|
|
pedigree = [character.senior_id | pedigree]
|
|
|
|
# Senior's senior
|
|
pedigree = if senior.senior_id != 0 do
|
|
[senior.senior_id | pedigree]
|
|
else
|
|
pedigree
|
|
end
|
|
|
|
# Senior's other junior
|
|
other_junior = if senior.junior1_id == character_id do
|
|
senior.junior2_id
|
|
else
|
|
senior.junior1_id
|
|
end
|
|
|
|
if other_junior != 0 do
|
|
[other_junior | pedigree]
|
|
else
|
|
pedigree
|
|
end
|
|
else
|
|
pedigree
|
|
end
|
|
else
|
|
pedigree
|
|
end
|
|
|
|
# Add juniors and their juniors
|
|
pedigree = if character.junior1_id != 0 do
|
|
junior1 = Map.get(family.members, character.junior1_id)
|
|
if junior1 do
|
|
pedigree = pedigree ++ [character.junior1_id]
|
|
|
|
if junior1.junior1_id != 0 do
|
|
pedigree ++ [junior1.junior1_id]
|
|
else
|
|
pedigree
|
|
end
|
|
|> then(fn p ->
|
|
if junior1.junior2_id != 0 do
|
|
p ++ [junior1.junior2_id]
|
|
else
|
|
p
|
|
end
|
|
end)
|
|
else
|
|
pedigree
|
|
end
|
|
else
|
|
pedigree
|
|
end
|
|
|
|
pedigree = if character.junior2_id != 0 do
|
|
junior2 = Map.get(family.members, character.junior2_id)
|
|
if junior2 do
|
|
pedigree = pedigree ++ [character.junior2_id]
|
|
|
|
if junior2.junior1_id != 0 do
|
|
pedigree ++ [junior2.junior1_id]
|
|
else
|
|
pedigree
|
|
end
|
|
|> then(fn p ->
|
|
if junior2.junior2_id != 0 do
|
|
p ++ [junior2.junior2_id]
|
|
else
|
|
p
|
|
end
|
|
end)
|
|
else
|
|
pedigree
|
|
end
|
|
else
|
|
pedigree
|
|
end
|
|
|
|
pedigree
|
|
end
|
|
end
|
|
|
|
defp recalculate_pedigrees(family) do
|
|
members = Enum.map(family.members, fn {id, member} ->
|
|
pedigree = calculate_pedigree(family, id)
|
|
descendants = count_descendants(family, id)
|
|
{id, %{member | pedigree: pedigree, descendants: descendants}}
|
|
end)
|
|
|> Map.new()
|
|
|
|
%{family | members: members}
|
|
end
|
|
|
|
defp count_descendants(family, character_id) do
|
|
character = Map.get(family.members, character_id)
|
|
if not character do
|
|
0
|
|
else
|
|
count = 0
|
|
|
|
count = if character.junior1_id != 0 do
|
|
count + 1 + count_descendants(family, character.junior1_id)
|
|
else
|
|
count
|
|
end
|
|
|
|
count = if character.junior2_id != 0 do
|
|
count + 1 + count_descendants(family, character.junior2_id)
|
|
else
|
|
count
|
|
end
|
|
|
|
count
|
|
end
|
|
end
|
|
|
|
# ============================================================================
|
|
# Database Functions (Stub implementations)
|
|
# ============================================================================
|
|
|
|
defp load_families_from_db do
|
|
# TODO: Implement actual database loading
|
|
%{}
|
|
end
|
|
|
|
defp create_family_in_db(_leader_id) do
|
|
# TODO: Implement database insert
|
|
{:ok, System.unique_integer([:positive])}
|
|
end
|
|
|
|
defp save_family_member_to_db(_family_id, _member) do
|
|
# TODO: Implement
|
|
:ok
|
|
end
|
|
|
|
defp update_member_in_db(_character_id, _member) do
|
|
# TODO: Implement
|
|
:ok
|
|
end
|
|
|
|
defp move_members_to_new_family(_new_leader_id, _new_family_id, _members) do
|
|
# TODO: Implement
|
|
:ok
|
|
end
|
|
|
|
defp merge_families_in_db(_new_family_id, _old_family_id, _member_ids) do
|
|
# TODO: Implement
|
|
:ok
|
|
end
|
|
|
|
defp remove_family_member_from_db(_family_id, _character_id) do
|
|
# TODO: Implement
|
|
:ok
|
|
end
|
|
|
|
defp update_family_notice_in_db(_family_id, _notice) do
|
|
# TODO: Implement
|
|
:ok
|
|
end
|
|
|
|
defp disband_family_in_db(_family_id) do
|
|
# TODO: Implement
|
|
:ok
|
|
end
|
|
|
|
# ============================================================================
|
|
# Broadcast Functions
|
|
# ============================================================================
|
|
|
|
defp broadcast_family_joined(family, junior) do
|
|
Logger.debug("Broadcast family join for #{junior.name} to family #{family.id}")
|
|
end
|
|
|
|
defp broadcast_family_disband(family) do
|
|
Logger.debug("Broadcast family disband for family #{family.id}")
|
|
end
|
|
|
|
defp broadcast_member_login(family, character, online) do
|
|
Logger.debug("Broadcast family member #{character.name} login=#{online} to family #{family.id}")
|
|
end
|
|
|
|
defp broadcast_member_levelup(family, character) do
|
|
Logger.debug("Broadcast family member #{character.name} levelup to family #{family.id}")
|
|
end
|
|
|
|
defp broadcast_member_jobchange(family, character) do
|
|
Logger.debug("Broadcast family member #{character.name} job change to family #{family.id}")
|
|
end
|
|
|
|
defp broadcast_rep_change(family, amount, name, character_id) do
|
|
Logger.debug("Broadcast family rep change #{amount} for #{name} in family #{family.id}")
|
|
end
|
|
end
|