kimi gone wild
This commit is contained in:
@@ -1,16 +1,917 @@
|
||||
defmodule Odinsea.World.Guild do
|
||||
@moduledoc """
|
||||
Guild management service.
|
||||
Ported from src/handling/world/guild/MapleGuild.java
|
||||
|
||||
Manages guild state including members, ranks, skills, and alliance.
|
||||
Supports guild creation, joining, leaving, and rank management.
|
||||
"""
|
||||
|
||||
use GenServer
|
||||
|
||||
require Logger
|
||||
|
||||
alias Odinsea.Database.Repo
|
||||
import Ecto.Query
|
||||
|
||||
@default_capacity 10
|
||||
@max_capacity 200
|
||||
@rank_titles ["Master", "Jr. Master", "Member", "Member", "Member"]
|
||||
@create_cost 500_000
|
||||
|
||||
# ============================================================================
|
||||
# Data Structures
|
||||
# ============================================================================
|
||||
|
||||
defmodule GuildCharacter do
|
||||
@moduledoc "Guild member representation"
|
||||
defstruct [
|
||||
:id, :name, :level, :job, :channel,
|
||||
:guild_rank, :alliance_rank, :guild_contribution,
|
||||
:online
|
||||
]
|
||||
end
|
||||
|
||||
defmodule GuildSkill do
|
||||
@moduledoc "Guild skill representation"
|
||||
defstruct [
|
||||
:skill_id, :level, :timestamp, :purchaser, :activators
|
||||
]
|
||||
end
|
||||
|
||||
defmodule BBSThread do
|
||||
@moduledoc "Guild BBS thread"
|
||||
defstruct [
|
||||
:thread_id, :local_id, :name, :content,
|
||||
:poster_id, :timestamp, :icon, :replies
|
||||
]
|
||||
end
|
||||
|
||||
# ============================================================================
|
||||
# Client API
|
||||
# ============================================================================
|
||||
|
||||
def start_link(_) do
|
||||
GenServer.start_link(__MODULE__, [], name: __MODULE__)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Creates a new guild.
|
||||
Returns {:ok, guild_id} on success, {:error, reason} on failure.
|
||||
"""
|
||||
def create_guild(leader_id, name) do
|
||||
GenServer.call(__MODULE__, {:create_guild, leader_id, name})
|
||||
end
|
||||
|
||||
@doc """
|
||||
Gets a guild by ID.
|
||||
Returns the guild struct or nil if not found.
|
||||
"""
|
||||
def get_guild(guild_id) do
|
||||
GenServer.call(__MODULE__, {:get_guild, guild_id})
|
||||
end
|
||||
|
||||
@doc """
|
||||
Gets guild by member character ID.
|
||||
"""
|
||||
def get_guild_by_character(character_id) do
|
||||
GenServer.call(__MODULE__, {:get_guild_by_character, character_id})
|
||||
end
|
||||
|
||||
@doc """
|
||||
Adds a member to a guild.
|
||||
"""
|
||||
def add_member(guild_id, character) do
|
||||
GenServer.call(__MODULE__, {:add_member, guild_id, character})
|
||||
end
|
||||
|
||||
@doc """
|
||||
Removes a member from a guild (leave).
|
||||
"""
|
||||
def leave_guild(guild_id, character_id) do
|
||||
GenServer.call(__MODULE__, {:leave_guild, guild_id, character_id})
|
||||
end
|
||||
|
||||
@doc """
|
||||
Expels a member from a guild.
|
||||
"""
|
||||
def expel_member(guild_id, expeller_id, target_id, target_name) do
|
||||
GenServer.call(__MODULE__, {:expel_member, guild_id, expeller_id, target_id, target_name})
|
||||
end
|
||||
|
||||
@doc """
|
||||
Changes a member's guild rank.
|
||||
"""
|
||||
def change_rank(guild_id, character_id, new_rank, changer_id) do
|
||||
GenServer.call(__MODULE__, {:change_rank, guild_id, character_id, new_rank, changer_id})
|
||||
end
|
||||
|
||||
@doc """
|
||||
Changes guild rank titles.
|
||||
"""
|
||||
def change_rank_titles(guild_id, titles, changer_id) do
|
||||
GenServer.call(__MODULE__, {:change_rank_titles, guild_id, titles, changer_id})
|
||||
end
|
||||
|
||||
@doc """
|
||||
Changes the guild leader.
|
||||
"""
|
||||
def change_leader(guild_id, new_leader_id, current_leader_id) do
|
||||
GenServer.call(__MODULE__, {:change_leader, guild_id, new_leader_id, current_leader_id})
|
||||
end
|
||||
|
||||
@doc """
|
||||
Sets guild emblem.
|
||||
"""
|
||||
def set_emblem(guild_id, bg, bg_color, logo, logo_color, changer_id) do
|
||||
GenServer.call(__MODULE__, {:set_emblem, guild_id, bg, bg_color, logo, logo_color, changer_id})
|
||||
end
|
||||
|
||||
@doc """
|
||||
Sets guild notice.
|
||||
"""
|
||||
def set_notice(guild_id, notice, changer_id) do
|
||||
GenServer.call(__MODULE__, {:set_notice, guild_id, notice, changer_id})
|
||||
end
|
||||
|
||||
@doc """
|
||||
Increases guild capacity.
|
||||
"""
|
||||
def increase_capacity(guild_id, leader_id, true_max \\ false) do
|
||||
GenServer.call(__MODULE__, {:increase_capacity, guild_id, leader_id, true_max})
|
||||
end
|
||||
|
||||
@doc """
|
||||
Gains guild points (GP).
|
||||
"""
|
||||
def gain_gp(guild_id, amount, character_id \\ nil, broadcast \\ true) do
|
||||
GenServer.call(__MODULE__, {:gain_gp, guild_id, amount, character_id, broadcast})
|
||||
end
|
||||
|
||||
@doc """
|
||||
Sets member online status.
|
||||
"""
|
||||
def set_online(guild_id, character_id, online, channel) do
|
||||
GenServer.call(__MODULE__, {:set_online, guild_id, character_id, online, channel})
|
||||
end
|
||||
|
||||
@doc """
|
||||
Updates member info (level/job change).
|
||||
"""
|
||||
def update_member(guild_id, character) do
|
||||
GenServer.call(__MODULE__, {:update_member, guild_id, character})
|
||||
end
|
||||
|
||||
@doc """
|
||||
Disbands a guild.
|
||||
"""
|
||||
def disband_guild(guild_id, leader_id) do
|
||||
GenServer.call(__MODULE__, {:disband_guild, guild_id, leader_id})
|
||||
end
|
||||
|
||||
@doc """
|
||||
Gets guild skills.
|
||||
"""
|
||||
def get_skills(guild_id) do
|
||||
GenServer.call(__MODULE__, {:get_skills, guild_id})
|
||||
end
|
||||
|
||||
@doc """
|
||||
Purchases a guild skill.
|
||||
"""
|
||||
def purchase_skill(guild_id, skill_id, purchaser_name, purchaser_id) do
|
||||
GenServer.call(__MODULE__, {:purchase_skill, guild_id, skill_id, purchaser_name, purchaser_id})
|
||||
end
|
||||
|
||||
@doc """
|
||||
Activates a guild skill.
|
||||
"""
|
||||
def activate_skill(guild_id, skill_id, activator_name) do
|
||||
GenServer.call(__MODULE__, {:activate_skill, guild_id, skill_id, activator_name})
|
||||
end
|
||||
|
||||
@doc """
|
||||
Broadcasts a packet to all online guild members.
|
||||
"""
|
||||
def broadcast(guild_id, packet, except_character_id \\ nil) do
|
||||
GenServer.cast(__MODULE__, {:broadcast, guild_id, packet, except_character_id})
|
||||
end
|
||||
|
||||
@doc """
|
||||
Guild chat - sends message to all online guild members.
|
||||
"""
|
||||
def guild_chat(guild_id, sender_name, sender_id, message) do
|
||||
GenServer.cast(__MODULE__, {:guild_chat, guild_id, sender_name, sender_id, message})
|
||||
end
|
||||
|
||||
@doc """
|
||||
Sets alliance ID for a guild.
|
||||
"""
|
||||
def set_alliance(guild_id, alliance_id, alliance_rank) do
|
||||
GenServer.call(__MODULE__, {:set_alliance, guild_id, alliance_id, alliance_rank})
|
||||
end
|
||||
|
||||
# ============================================================================
|
||||
# Server Callbacks
|
||||
# ============================================================================
|
||||
|
||||
@impl true
|
||||
def init(_) do
|
||||
{:ok, %{guilds: %{}}}
|
||||
# Load guilds from database on startup
|
||||
guilds = load_guilds_from_db()
|
||||
|
||||
Logger.info("Guild service initialized with #{map_size(guilds)} guilds")
|
||||
{:ok, %{guilds: guilds}}
|
||||
end
|
||||
|
||||
@impl true
|
||||
def handle_call({:create_guild, leader_id, name}, _from, state) do
|
||||
# Validate name
|
||||
cond do
|
||||
String.length(name) > 12 ->
|
||||
{:reply, {:error, :name_too_long}, state}
|
||||
|
||||
String.length(name) < 3 ->
|
||||
{:reply, {:error, :name_too_short}, state}
|
||||
|
||||
not valid_guild_name?(name) ->
|
||||
{:reply, {:error, :invalid_name}, state}
|
||||
|
||||
true ->
|
||||
case create_guild_in_db(leader_id, name) do
|
||||
{:ok, guild_id} ->
|
||||
guild = create_new_guild_struct(guild_id, leader_id, name)
|
||||
new_state = %{state | guilds: Map.put(state.guilds, guild_id, guild)}
|
||||
|
||||
Logger.info("Guild '#{name}' (ID: #{guild_id}) created by leader #{leader_id}")
|
||||
{:reply, {:ok, guild_id}, new_state}
|
||||
|
||||
{:error, reason} ->
|
||||
{:reply, {:error, reason}, state}
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@impl true
|
||||
def handle_call({:get_guild, guild_id}, _from, state) do
|
||||
{:reply, Map.get(state.guilds, guild_id), state}
|
||||
end
|
||||
|
||||
@impl true
|
||||
def handle_call({:get_guild_by_character, character_id}, _from, state) do
|
||||
guild = state.guilds
|
||||
|> Map.values()
|
||||
|> Enum.find(fn g ->
|
||||
Enum.any?(g.members, fn m -> m.id == character_id end)
|
||||
end)
|
||||
|
||||
{:reply, guild, state}
|
||||
end
|
||||
|
||||
@impl true
|
||||
def handle_call({:add_member, guild_id, character}, _from, state) do
|
||||
case Map.get(state.guilds, guild_id) do
|
||||
nil ->
|
||||
{:reply, {:error, :guild_not_found}, state}
|
||||
|
||||
guild ->
|
||||
if length(guild.members) >= guild.capacity do
|
||||
{:reply, {:error, :guild_full}, state}
|
||||
else
|
||||
# Create new member with rank 5 (lowest)
|
||||
member = %GuildCharacter{
|
||||
id: character.id,
|
||||
name: character.name,
|
||||
level: character.level,
|
||||
job: character.job,
|
||||
channel: character.channel_id || 1,
|
||||
guild_rank: 5,
|
||||
alliance_rank: if(guild.alliance_id > 0, do: 3, else: 0),
|
||||
guild_contribution: 0,
|
||||
online: true
|
||||
}
|
||||
|
||||
updated_guild = %{guild | members: guild.members ++ [member]}
|
||||
|
||||
# Save to database
|
||||
save_member_to_db(guild_id, member)
|
||||
|
||||
# Broadcast new member to guild
|
||||
broadcast_new_member(updated_guild, member)
|
||||
|
||||
# Gain GP for new member
|
||||
updated_guild = %{updated_guild | gp: guild.gp + 500}
|
||||
|
||||
{:reply, {:ok, member}, %{state | guilds: Map.put(state.guilds, guild_id, updated_guild)}}
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@impl true
|
||||
def handle_call({:leave_guild, guild_id, character_id}, _from, state) do
|
||||
case Map.get(state.guilds, guild_id) do
|
||||
nil ->
|
||||
{:reply, {:error, :guild_not_found}, state}
|
||||
|
||||
guild ->
|
||||
member = Enum.find(guild.members, fn m -> m.id == character_id end)
|
||||
|
||||
if member do
|
||||
# Remove member
|
||||
members = Enum.reject(guild.members, fn m -> m.id == character_id end)
|
||||
|
||||
# If leader leaves and there are members, promote next highest rank
|
||||
updated_guild = if guild.leader_id == character_id && length(members) > 0 do
|
||||
# Find highest ranked member (lowest rank number)
|
||||
new_leader = Enum.min_by(members, fn m -> m.guild_rank end)
|
||||
%{guild | members: members, leader_id: new_leader.id}
|
||||
else
|
||||
%{guild | members: members}
|
||||
end
|
||||
|
||||
# Remove from database
|
||||
remove_member_from_db(guild_id, character_id)
|
||||
|
||||
# Broadcast member left
|
||||
broadcast_member_left(updated_guild, member)
|
||||
|
||||
# Deduct GP
|
||||
gp_loss = if member.guild_contribution > 0, do: -member.guild_contribution, else: -50
|
||||
updated_guild = %{updated_guild | gp: max(0, guild.gp + gp_loss)}
|
||||
|
||||
# If no members left, mark for disband
|
||||
final_state = if length(members) == 0 do
|
||||
disband_guild_in_db(guild_id)
|
||||
%{state | guilds: Map.delete(state.guilds, guild_id)}
|
||||
else
|
||||
%{state | guilds: Map.put(state.guilds, guild_id, updated_guild)}
|
||||
end
|
||||
|
||||
{:reply, :ok, final_state}
|
||||
else
|
||||
{:reply, {:error, :not_in_guild}, state}
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@impl true
|
||||
def handle_call({:expel_member, guild_id, expeller_id, target_id, target_name}, _from, state) do
|
||||
case Map.get(state.guilds, guild_id) do
|
||||
nil ->
|
||||
{:reply, {:error, :guild_not_found}, state}
|
||||
|
||||
guild ->
|
||||
expeller = Enum.find(guild.members, fn m -> m.id == expeller_id end)
|
||||
target = Enum.find(guild.members, fn m -> m.id == target_id end)
|
||||
|
||||
cond do
|
||||
not expeller || expeller.guild_rank > 2 ->
|
||||
{:reply, {:error, :no_permission}, state}
|
||||
|
||||
not target || target.guild_rank <= expeller.guild_rank ->
|
||||
{:reply, {:error, :cannot_expel}, state}
|
||||
|
||||
true ->
|
||||
# Remove member
|
||||
members = Enum.reject(guild.members, fn m -> m.id == target_id end)
|
||||
updated_guild = %{guild | members: members}
|
||||
|
||||
# Remove from database
|
||||
remove_member_from_db(guild_id, target_id)
|
||||
|
||||
# Send note if offline
|
||||
unless target.online do
|
||||
send_note(target_name, expeller.name, "You have been expelled from the guild.")
|
||||
end
|
||||
|
||||
# Broadcast
|
||||
broadcast_member_expelled(updated_guild, target)
|
||||
|
||||
# Deduct GP
|
||||
gp_loss = if target.guild_contribution > 0, do: -target.guild_contribution, else: -50
|
||||
updated_guild = %{updated_guild | gp: max(0, guild.gp + gp_loss)}
|
||||
|
||||
{:reply, :ok, %{state | guilds: Map.put(state.guilds, guild_id, updated_guild)}}
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@impl true
|
||||
def handle_call({:change_rank, guild_id, character_id, new_rank, changer_id}, _from, state) do
|
||||
case Map.get(state.guilds, guild_id) do
|
||||
nil ->
|
||||
{:reply, {:error, :guild_not_found}, state}
|
||||
|
||||
guild ->
|
||||
changer = Enum.find(guild.members, fn m -> m.id == changer_id end)
|
||||
target = Enum.find(guild.members, fn m -> m.id == character_id end)
|
||||
|
||||
cond do
|
||||
not changer || changer.guild_rank > 2 ->
|
||||
{:reply, {:error, :no_permission}, state}
|
||||
|
||||
new_rank <= 1 || new_rank > 5 ->
|
||||
{:reply, {:error, :invalid_rank}, state}
|
||||
|
||||
new_rank <= 2 && changer.guild_rank != 1 ->
|
||||
{:reply, {:error, :no_permission}, state}
|
||||
|
||||
not target ->
|
||||
{:reply, {:error, :member_not_found}, state}
|
||||
|
||||
true ->
|
||||
# Update rank
|
||||
members = Enum.map(guild.members, fn m ->
|
||||
if m.id == character_id do
|
||||
%{m | guild_rank: new_rank}
|
||||
else
|
||||
m
|
||||
end
|
||||
end)
|
||||
|
||||
updated_guild = %{guild | members: members}
|
||||
|
||||
# Save to database
|
||||
update_rank_in_db(character_id, new_rank)
|
||||
|
||||
# Broadcast
|
||||
broadcast_rank_changed(updated_guild, target, new_rank)
|
||||
|
||||
{:reply, :ok, %{state | guilds: Map.put(state.guilds, guild_id, updated_guild)}}
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@impl true
|
||||
def handle_call({:change_rank_titles, guild_id, titles, changer_id}, _from, state) do
|
||||
case Map.get(state.guilds, guild_id) do
|
||||
nil ->
|
||||
{:reply, {:error, :guild_not_found}, state}
|
||||
|
||||
guild ->
|
||||
changer = Enum.find(guild.members, fn m -> m.id == changer_id end)
|
||||
|
||||
if not changer || changer.guild_rank != 1 do
|
||||
{:reply, {:error, :no_permission}, state}
|
||||
else
|
||||
updated_guild = %{guild | rank_titles: titles}
|
||||
|
||||
# Save to database
|
||||
update_rank_titles_in_db(guild_id, titles)
|
||||
|
||||
# Broadcast
|
||||
broadcast_rank_titles_changed(updated_guild, titles)
|
||||
|
||||
{:reply, :ok, %{state | guilds: Map.put(state.guilds, guild_id, updated_guild)}}
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@impl true
|
||||
def handle_call({:change_leader, guild_id, new_leader_id, current_leader_id}, _from, state) do
|
||||
case Map.get(state.guilds, guild_id) do
|
||||
nil ->
|
||||
{:reply, {:error, :guild_not_found}, state}
|
||||
|
||||
%{leader_id: actual_leader} when actual_leader != current_leader_id ->
|
||||
{:reply, {:error, :not_leader}, state}
|
||||
|
||||
guild ->
|
||||
unless Enum.any?(guild.members, fn m -> m.id == new_leader_id end) do
|
||||
{:reply, {:error, :not_in_guild}, state}
|
||||
else
|
||||
# Update ranks: new leader -> 1, old leader -> 2
|
||||
members = Enum.map(guild.members, fn m ->
|
||||
cond do
|
||||
m.id == new_leader_id -> %{m | guild_rank: 1}
|
||||
m.id == current_leader_id -> %{m | guild_rank: 2}
|
||||
true -> m
|
||||
end
|
||||
end)
|
||||
|
||||
updated_guild = %{guild | members: members, leader_id: new_leader_id}
|
||||
|
||||
# Save to database
|
||||
update_leader_in_db(guild_id, new_leader_id)
|
||||
update_rank_in_db(new_leader_id, 1)
|
||||
update_rank_in_db(current_leader_id, 2)
|
||||
|
||||
# Broadcast
|
||||
broadcast_leader_changed(updated_guild, new_leader_id)
|
||||
|
||||
Logger.info("Guild #{guild_id} leader changed from #{current_leader_id} to #{new_leader_id}")
|
||||
{:reply, :ok, %{state | guilds: Map.put(state.guilds, guild_id, updated_guild)}}
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@impl true
|
||||
def handle_call({:set_emblem, guild_id, bg, bg_color, logo, logo_color, changer_id}, _from, state) do
|
||||
case Map.get(state.guilds, guild_id) do
|
||||
nil ->
|
||||
{:reply, {:error, :guild_not_found}, state}
|
||||
|
||||
guild ->
|
||||
changer = Enum.find(guild.members, fn m -> m.id == changer_id end)
|
||||
|
||||
if not changer || changer.guild_rank != 1 do
|
||||
{:reply, {:error, :no_permission}, state}
|
||||
else
|
||||
updated_guild = %{guild |
|
||||
logo_bg: bg,
|
||||
logo_bg_color: bg_color,
|
||||
logo: logo,
|
||||
logo_color: logo_color
|
||||
}
|
||||
|
||||
# Save to database
|
||||
update_emblem_in_db(guild_id, bg, bg_color, logo, logo_color)
|
||||
|
||||
# Broadcast
|
||||
broadcast_emblem_changed(updated_guild)
|
||||
|
||||
{:reply, :ok, %{state | guilds: Map.put(state.guilds, guild_id, updated_guild)}}
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@impl true
|
||||
def handle_call({:set_notice, guild_id, notice, changer_id}, _from, state) do
|
||||
case Map.get(state.guilds, guild_id) do
|
||||
nil ->
|
||||
{:reply, {:error, :guild_not_found}, state}
|
||||
|
||||
guild ->
|
||||
changer = Enum.find(guild.members, fn m -> m.id == changer_id end)
|
||||
|
||||
if not changer || changer.guild_rank > 2 do
|
||||
{:reply, {:error, :no_permission}, state}
|
||||
else
|
||||
updated_guild = %{guild | notice: String.slice(notice, 0, 100)}
|
||||
|
||||
# Save to database
|
||||
update_notice_in_db(guild_id, updated_guild.notice)
|
||||
|
||||
# Broadcast
|
||||
broadcast_notice_changed(updated_guild)
|
||||
|
||||
{:reply, :ok, %{state | guilds: Map.put(state.guilds, guild_id, updated_guild)}}
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@impl true
|
||||
def handle_call({:increase_capacity, guild_id, leader_id, true_max}, _from, state) do
|
||||
case Map.get(state.guilds, guild_id) do
|
||||
nil ->
|
||||
{:reply, {:error, :guild_not_found}, state}
|
||||
|
||||
guild ->
|
||||
max_cap = if true_max, do: @max_capacity, else: div(@max_capacity, 2)
|
||||
|
||||
cond do
|
||||
guild.leader_id != leader_id ->
|
||||
{:reply, {:error, :not_leader}, state}
|
||||
|
||||
guild.capacity >= max_cap ->
|
||||
{:reply, {:error, :max_capacity}, state}
|
||||
|
||||
true ->
|
||||
new_capacity = min(guild.capacity + 5, max_cap)
|
||||
updated_guild = %{guild | capacity: new_capacity}
|
||||
|
||||
# Save to database
|
||||
update_capacity_in_db(guild_id, new_capacity)
|
||||
|
||||
# Broadcast
|
||||
broadcast_capacity_changed(updated_guild)
|
||||
|
||||
{:reply, {:ok, new_capacity}, %{state | guilds: Map.put(state.guilds, guild_id, updated_guild)}}
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@impl true
|
||||
def handle_call({:gain_gp, guild_id, amount, _character_id, broadcast}, _from, state) do
|
||||
case Map.get(state.guilds, guild_id) do
|
||||
nil ->
|
||||
{:reply, {:error, :guild_not_found}, state}
|
||||
|
||||
guild ->
|
||||
new_gp = max(0, guild.gp + amount)
|
||||
updated_guild = %{guild | gp: new_gp}
|
||||
|
||||
# Save to database
|
||||
update_gp_in_db(guild_id, new_gp)
|
||||
|
||||
# Optionally broadcast
|
||||
if broadcast do
|
||||
broadcast_gp_changed(updated_guild, amount)
|
||||
end
|
||||
|
||||
{:reply, {:ok, new_gp}, %{state | guilds: Map.put(state.guilds, guild_id, updated_guild)}}
|
||||
end
|
||||
end
|
||||
|
||||
@impl true
|
||||
def handle_call({:set_online, guild_id, character_id, online, channel}, _from, state) do
|
||||
case Map.get(state.guilds, guild_id) do
|
||||
nil ->
|
||||
{:reply, {:error, :guild_not_found}, state}
|
||||
|
||||
guild ->
|
||||
members = Enum.map(guild.members, fn m ->
|
||||
if m.id == character_id do
|
||||
%{m | online: online, channel: if(online, do: channel, else: -1)}
|
||||
else
|
||||
m
|
||||
end
|
||||
end)
|
||||
|
||||
updated_guild = %{guild | members: members}
|
||||
|
||||
# Broadcast online status to other members
|
||||
broadcast_member_online(updated_guild, character_id, online)
|
||||
|
||||
{:reply, :ok, %{state | guilds: Map.put(state.guilds, guild_id, updated_guild)}}
|
||||
end
|
||||
end
|
||||
|
||||
@impl true
|
||||
def handle_call({:update_member, guild_id, character}, _from, state) do
|
||||
case Map.get(state.guilds, guild_id) do
|
||||
nil ->
|
||||
{:reply, {:error, :guild_not_found}, state}
|
||||
|
||||
guild ->
|
||||
members = Enum.map(guild.members, fn m ->
|
||||
if m.id == character.id do
|
||||
%{m |
|
||||
level: character.level || m.level,
|
||||
job: character.job || m.job,
|
||||
channel: character.channel_id || m.channel
|
||||
}
|
||||
else
|
||||
m
|
||||
end
|
||||
end)
|
||||
|
||||
updated_guild = %{guild | members: members}
|
||||
|
||||
# Broadcast level/job change if applicable
|
||||
broadcast_member_info_updated(updated_guild, character)
|
||||
|
||||
{:reply, :ok, %{state | guilds: Map.put(state.guilds, guild_id, updated_guild)}}
|
||||
end
|
||||
end
|
||||
|
||||
@impl true
|
||||
def handle_call({:disband_guild, guild_id, leader_id}, _from, state) do
|
||||
case Map.get(state.guilds, guild_id) do
|
||||
nil ->
|
||||
{:reply, {:error, :guild_not_found}, state}
|
||||
|
||||
%{leader_id: actual_leader} when actual_leader != leader_id ->
|
||||
{:reply, {:error, :not_leader}, state}
|
||||
|
||||
guild ->
|
||||
# Broadcast disband
|
||||
broadcast_guild_disband(guild)
|
||||
|
||||
# Remove from database
|
||||
disband_guild_in_db(guild_id)
|
||||
|
||||
Logger.info("Guild #{guild_id} (#{guild.name}) disbanded")
|
||||
{:reply, :ok, %{state | guilds: Map.delete(state.guilds, guild_id)}}
|
||||
end
|
||||
end
|
||||
|
||||
@impl true
|
||||
def handle_call({:set_alliance, guild_id, alliance_id, alliance_rank}, _from, state) do
|
||||
case Map.get(state.guilds, guild_id) do
|
||||
nil ->
|
||||
{:reply, {:error, :guild_not_found}, state}
|
||||
|
||||
guild ->
|
||||
members = Enum.map(guild.members, fn m ->
|
||||
%{m | alliance_rank: alliance_rank}
|
||||
end)
|
||||
|
||||
updated_guild = %{guild | alliance_id: alliance_id, members: members}
|
||||
|
||||
# Save to database
|
||||
update_alliance_in_db(guild_id, alliance_id, alliance_rank)
|
||||
|
||||
{:reply, :ok, %{state | guilds: Map.put(state.guilds, guild_id, updated_guild)}}
|
||||
end
|
||||
end
|
||||
|
||||
@impl true
|
||||
def handle_cast({:broadcast, guild_id, packet, except_id}, state) do
|
||||
case Map.get(state.guilds, guild_id) do
|
||||
nil -> :ok
|
||||
guild ->
|
||||
Enum.each(guild.members, fn member ->
|
||||
if member.online && member.id != except_id do
|
||||
case Registry.lookup(Odinsea.CharacterRegistry, member.id) do
|
||||
[{pid, _}] -> send(pid, {:send_packet, packet})
|
||||
[] -> :ok
|
||||
end
|
||||
end
|
||||
end)
|
||||
end
|
||||
|
||||
{:noreply, state}
|
||||
end
|
||||
|
||||
@impl true
|
||||
def handle_cast({:guild_chat, guild_id, sender_name, sender_id, message}, state) do
|
||||
case Map.get(state.guilds, guild_id) do
|
||||
nil -> :ok
|
||||
guild ->
|
||||
Enum.each(guild.members, fn member ->
|
||||
if member.online && member.id != sender_id do
|
||||
# Check blacklist
|
||||
# TODO: Implement blacklist check
|
||||
|
||||
case Registry.lookup(Odinsea.CharacterRegistry, member.id) do
|
||||
[{pid, _}] ->
|
||||
packet = build_guild_chat_packet(sender_name, message)
|
||||
send(pid, {:send_packet, packet})
|
||||
[] -> :ok
|
||||
end
|
||||
end
|
||||
end)
|
||||
end
|
||||
|
||||
{:noreply, state}
|
||||
end
|
||||
|
||||
# ============================================================================
|
||||
# Database Functions (Stub implementations - would use Ecto)
|
||||
# ============================================================================
|
||||
|
||||
defp load_guilds_from_db do
|
||||
# TODO: Implement actual database loading
|
||||
# For now, return empty map
|
||||
%{}
|
||||
end
|
||||
|
||||
defp create_guild_in_db(leader_id, name) do
|
||||
# TODO: Implement database insert
|
||||
# Return a new guild ID
|
||||
{:ok, System.unique_integer([:positive])}
|
||||
end
|
||||
|
||||
defp save_member_to_db(_guild_id, _member) do
|
||||
# TODO: Implement
|
||||
:ok
|
||||
end
|
||||
|
||||
defp remove_member_from_db(_guild_id, _character_id) do
|
||||
# TODO: Implement
|
||||
:ok
|
||||
end
|
||||
|
||||
defp update_rank_in_db(_character_id, _rank) do
|
||||
# TODO: Implement
|
||||
:ok
|
||||
end
|
||||
|
||||
defp update_leader_in_db(_guild_id, _leader_id) do
|
||||
# TODO: Implement
|
||||
:ok
|
||||
end
|
||||
|
||||
defp update_rank_titles_in_db(_guild_id, _titles) do
|
||||
# TODO: Implement
|
||||
:ok
|
||||
end
|
||||
|
||||
defp update_emblem_in_db(_guild_id, _bg, _bg_color, _logo, _logo_color) do
|
||||
# TODO: Implement
|
||||
:ok
|
||||
end
|
||||
|
||||
defp update_notice_in_db(_guild_id, _notice) do
|
||||
# TODO: Implement
|
||||
:ok
|
||||
end
|
||||
|
||||
defp update_capacity_in_db(_guild_id, _capacity) do
|
||||
# TODO: Implement
|
||||
:ok
|
||||
end
|
||||
|
||||
defp update_gp_in_db(_guild_id, _gp) do
|
||||
# TODO: Implement
|
||||
:ok
|
||||
end
|
||||
|
||||
defp update_alliance_in_db(_guild_id, _alliance_id, _alliance_rank) do
|
||||
# TODO: Implement
|
||||
:ok
|
||||
end
|
||||
|
||||
defp disband_guild_in_db(_guild_id) do
|
||||
# TODO: Implement
|
||||
:ok
|
||||
end
|
||||
|
||||
defp send_note(_to_name, _from_name, _message) do
|
||||
# TODO: Implement note sending
|
||||
:ok
|
||||
end
|
||||
|
||||
# ============================================================================
|
||||
# Helper Functions
|
||||
# ============================================================================
|
||||
|
||||
defp valid_guild_name?(name) do
|
||||
# Only allow letters
|
||||
Regex.match?(~r/^[a-zA-Z]+$/, name)
|
||||
end
|
||||
|
||||
defp create_new_guild_struct(guild_id, leader_id, name) do
|
||||
%{
|
||||
id: guild_id,
|
||||
name: name,
|
||||
leader_id: leader_id,
|
||||
gp: 0,
|
||||
logo: 0,
|
||||
logo_color: 0,
|
||||
logo_bg: 0,
|
||||
logo_bg_color: 0,
|
||||
capacity: @default_capacity,
|
||||
rank_titles: @rank_titles,
|
||||
notice: "",
|
||||
signature: System.system_time(:second),
|
||||
alliance_id: 0,
|
||||
members: [],
|
||||
skills: %{},
|
||||
bbs: %{}
|
||||
}
|
||||
end
|
||||
|
||||
# ============================================================================
|
||||
# Broadcast Functions
|
||||
# ============================================================================
|
||||
|
||||
defp broadcast_new_member(guild, member) do
|
||||
# TODO: Implement packet
|
||||
Logger.debug("Broadcast new member #{member.name} to guild #{guild.id}")
|
||||
end
|
||||
|
||||
defp broadcast_member_left(guild, member) do
|
||||
Logger.debug("Broadcast member left #{member.name} to guild #{guild.id}")
|
||||
end
|
||||
|
||||
defp broadcast_member_expelled(guild, member) do
|
||||
Logger.debug("Broadcast member expelled #{member.name} to guild #{guild.id}")
|
||||
end
|
||||
|
||||
defp broadcast_rank_changed(guild, member, new_rank) do
|
||||
Logger.debug("Broadcast rank change for #{member.name} to #{new_rank} in guild #{guild.id}")
|
||||
end
|
||||
|
||||
defp broadcast_rank_titles_changed(guild, titles) do
|
||||
Logger.debug("Broadcast rank titles changed in guild #{guild.id}: #{inspect(titles)}")
|
||||
end
|
||||
|
||||
defp broadcast_leader_changed(guild, new_leader_id) do
|
||||
Logger.debug("Broadcast leader changed to #{new_leader_id} in guild #{guild.id}")
|
||||
end
|
||||
|
||||
defp broadcast_emblem_changed(guild) do
|
||||
Logger.debug("Broadcast emblem changed in guild #{guild.id}")
|
||||
end
|
||||
|
||||
defp broadcast_notice_changed(guild) do
|
||||
Logger.debug("Broadcast notice changed in guild #{guild.id}")
|
||||
end
|
||||
|
||||
defp broadcast_capacity_changed(guild) do
|
||||
Logger.debug("Broadcast capacity changed to #{guild.capacity} in guild #{guild.id}")
|
||||
end
|
||||
|
||||
defp broadcast_gp_changed(guild, amount) do
|
||||
Logger.debug("Broadcast GP change #{amount} in guild #{guild.id}")
|
||||
end
|
||||
|
||||
defp broadcast_member_online(guild, character_id, online) do
|
||||
Logger.debug("Broadcast member #{character_id} online=#{online} in guild #{guild.id}")
|
||||
end
|
||||
|
||||
defp broadcast_member_info_updated(guild, character) do
|
||||
Logger.debug("Broadcast member info update for #{character.id} in guild #{guild.id}")
|
||||
end
|
||||
|
||||
defp broadcast_guild_disband(guild) do
|
||||
Logger.debug("Broadcast guild disband for #{guild.id}")
|
||||
end
|
||||
|
||||
defp build_guild_chat_packet(_sender_name, _message) do
|
||||
# TODO: Implement proper packet
|
||||
<<>>
|
||||
end
|
||||
end
|
||||
|
||||
Reference in New Issue
Block a user