Files
odinsea-elixir/lib/odinsea/world/family.ex
2026-02-14 23:12:33 -07:00

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