Entity
Fellowship.cs
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using ACE.Common;
using ACE.Ensaty;
using ACE.Ensaty.Enum;
using ACE.Server.Managers;
using ACE.Server.Network.GameEvent.Events;
using ACE.Server.Network.GameMessages.Messages;
using ACE.Server.Network.Structure;
using ACE.Server.WorldObjects;
using log4net;
namespace ACE.Server.Ensaty
{
public clast Fellowship
{
private static readonly ILog log = LogManager.GetLogger(System.Reflection.MethodBase.GetCurrentMethod().DeclaringType);
///
/// The maximum # of fellowship members
///
public static int MaxFellows = 9;
public string FellowshipName;
public uint FellowshipLeaderGuid;
public bool DesiredShareXP; // determined by the leader's 'ShareFellowshipExpAndLuminance' client option when fellowship is created
public bool ShareLoot; // determined by the leader's 'ShareFellowshipLoot' client option when fellowship is created
public bool ShareXP; // whether or not XP sharing is currently enabled, as determined by DesiredShareXP && level restrictions
public bool EvenShare; // true if all fellows are >= level 50, or all fellows are within 5 levels of the leader
public bool Open; // indicates if non-leaders can invite new fellowship members
public bool IsLocked; // only set through emotes. if a fellowship is locked, new fellowship members cannot be added
public Dictionary FellowshipMembers;
public Dictionary DepartedMembers;
public Dictionary FellowshipLocks;
public QuestManager QuestManager;
///
/// Called when a player first creates a Fellowship
///
public Fellowship(Player leader, string fellowshipName, bool shareXP)
{
DesiredShareXP = shareXP;
ShareXP = shareXP;
// get loot sharing from leader's character options
ShareLoot = leader.GetCharacterOption(CharacterOption.ShareFellowshipLoot);
FellowshipLeaderGuid = leader.Guid.Full;
FellowshipName = fellowshipName;
EvenShare = false;
FellowshipMembers = new Dictionary() { { leader.Guid.Full, new WeakReference(leader) } };
Open = false;
QuestManager = new QuestManager(this);
IsLocked = false;
DepartedMembers = new Dictionary();
FellowshipLocks = new Dictionary();
}
///
/// Called when a player clicks the 'add fellow' button
///
public void AddFellowshipMember(Player inviter, Player newMember)
{
if (inviter == null || newMember == null)
return;
if (IsLocked)
{
if (!DepartedMembers.TryGetValue(newMember.Guid.Full, out var timeDeparted))
{
inviter.Session.Network.EnqueueSend(new GameEventWeenieErrorWithString(inviter.Session, WeenieErrorWithString.LockedFellowshipCannotRecruit_, newMember.Name));
//newMember.SendWeenieError(WeenieError.LockedFellowshipCannotRecruitYou);
return;
}
else
{
var timeLimit = Time.GetDateTimeFromTimestamp(timeDeparted).AddSeconds(600);
if (DateTime.UtcNow > timeLimit)
{
inviter.Session.Network.EnqueueSend(new GameEventWeenieErrorWithString(inviter.Session, WeenieErrorWithString.LockedFellowshipCannotRecruit_, newMember.Name));
//newMember.SendWeenieError(WeenieError.LockedFellowshipCannotRecruitYou);
return;
}
}
}
if (FellowshipMembers.Count == MaxFellows)
{
inviter.Session.Network.EnqueueSend(new GameEventWeenieError(inviter.Session, WeenieError.YourFellowshipIsFull));
return;
}
if (newMember.Fellowship != null || FellowshipMembers.ContainsKey(newMember.Guid.Full))
{
inviter.Session.Network.EnqueueSend(new GameMessageSystemChat($"{newMember.Name} is already a member of a Fellowship.", ChatMessageType.Broadcast));
}
else
{
if (PropertyManager.GetBool("fellow_busy_no_recruit").Item && newMember.IsBusy)
{
inviter.Session.Network.EnqueueSend(new GameMessageSystemChat($"{newMember.Name} is busy.", ChatMessageType.Broadcast));
return;
}
if (newMember.GetCharacterOption(CharacterOption.AutomaticallyAcceptFellowshipRequests))
{
AddConfirmedMember(inviter, newMember, true);
}
else
{
if (!newMember.ConfirmationManager.EnqueueSend(new Confirmation_Fellowship(inviter.Guid, newMember.Guid), inviter.Name))
{
inviter.Session.Network.EnqueueSend(new GameMessageSystemChat($"{newMember.Name} is busy.", ChatMessageType.Broadcast));
}
}
}
}
///
/// Finalizes the process of adding a player to the fellowship
/// If the player doesn't have the 'automatically accept fellowship requests' option set,
/// this would be after they responded to the popup window
///
public void AddConfirmedMember(Player inviter, Player player, bool response)
{
if (inviter == null || inviter.Session == null || inviter.Session.Player == null || player == null) return;
if (!response)
{
// player clicked 'no' on the fellowship popup
inviter.Session.Network.EnqueueSend(new GameMessageSystemChat($"{player.Name} declines your invite", ChatMessageType.Fellowship));
inviter.Session.Network.EnqueueSend(new GameEventWeenieError(inviter.Session, WeenieError.FellowshipDeclined));
return;
}
if (FellowshipMembers.Count == 9)
{
inviter.Session.Network.EnqueueSend(new GameEventWeenieError(inviter.Session, WeenieError.YourFellowshipIsFull));
return;
}
FellowshipMembers.TryAdd(player.Guid.Full, new WeakReference(player));
player.Fellowship = inviter.Fellowship;
CalculateXPSharing();
var fellowshipMembers = GetFellowshipMembers();
foreach (var member in fellowshipMembers.Values.Where(i => i.Guid != player.Guid))
member.Session.Network.EnqueueSend(new GameEventFellowshipUpdateFellow(member.Session, player, ShareXP));
if (ShareLoot)
{
foreach (var member in fellowshipMembers.Values.Where(i => i.Guid != player.Guid))
{
member.Session.Network.EnqueueSend(new GameMessageSystemChat($"{player.Name} has given you permission to loot his or her kills.", ChatMessageType.Broadcast));
member.Session.Network.EnqueueSend(new GameMessageSystemChat($"{player.Name} may now loot your kills.", ChatMessageType.Broadcast));
player.Session.Network.EnqueueSend(new GameMessageSystemChat($"{member.Name} has given you permission to loot his or her kills.", ChatMessageType.Broadcast));
player.Session.Network.EnqueueSend(new GameMessageSystemChat($"{member.Name} may now loot your kills.", ChatMessageType.Broadcast));
}
}
UpdateAllMembers();
if (inviter.CurrentMotionState.Stance == MotionStance.NonCombat) // only do this motion if inviter is at peace, other times motion is skipped.
inviter.SendMotionAsCommands(MotionCommand.BowDeep, MotionStance.NonCombat);
}
public void RemoveFellowshipMember(Player player, Player leader)
{
if (player == null) return;
var fellowshipMembers = GetFellowshipMembers();
if (!fellowshipMembers.ContainsKey(player.Guid.Full))
{
log.Warn($"{leader.Name} tried to dismiss {player.Name} from the fellowship, but {player.Name} was not found in the fellowship");
var done = true;
if (player.Fellowship != null)
{
if (player.Fellowship == this)
{
log.Warn($"{player.Name} still has a reference to this fellowship somehow. This shouldn't happen");
done = false;
}
else
log.Warn($"{player.Name} has a reference to a different fellowship. {leader.Name} is possibly sending crafted data!");
}
if (done) return;
}
foreach (var member in fellowshipMembers.Values)
{
member.Session.Network.EnqueueSend(new GameEventFellowshipDismiss(member.Session, player));
member.Session.Network.EnqueueSend(new GameMessageSystemChat($"{player.Name} dismissed from fellowship", ChatMessageType.Fellowship));
}
FellowshipMembers.Remove(player.Guid.Full);
player.Fellowship = null;
CalculateXPSharing();
UpdateAllMembers();
}
private void UpdateAllMembers()
{
var fellowshipMembers = GetFellowshipMembers();
foreach (var member in fellowshipMembers.Values)
member.Session.Network.EnqueueSend(new GameEventFellowshipFullUpdate(member.Session));
}
private void SendMessageAndUpdate(string message)
{
var fellowshipMembers = GetFellowshipMembers();
foreach (var member in fellowshipMembers.Values)
{
member.Session.Network.EnqueueSend(new GameMessageSystemChat(message, ChatMessageType.Fellowship));
member.Session.Network.EnqueueSend(new GameEventFellowshipFullUpdate(member.Session));
}
}
private void SendBroadcastAndUpdate(string message)
{
var fellowshipMembers = GetFellowshipMembers();
foreach (var member in fellowshipMembers.Values)
{
member.Session.Network.EnqueueSend(new GameEventChannelBroadcast(member.Session, Channel.FellowBroadcast, "", message));
member.Session.Network.EnqueueSend(new GameEventFellowshipFullUpdate(member.Session));
}
}
public void BroadcastToFellow(string message)
{
var fellowshipMembers = GetFellowshipMembers();
foreach (var member in fellowshipMembers.Values)
member.Session.Network.EnqueueSend(new GameEventChannelBroadcast(member.Session, Channel.FellowBroadcast, "", message));
}
public void TellFellow(WorldObject sender, string message)
{
var fellowshipMembers = GetFellowshipMembers();
foreach (var member in fellowshipMembers.Values)
member.Session.Network.EnqueueSend(new GameEventChannelBroadcast(member.Session, Channel.Fellow, sender.Name, message));
}
private void SendWeenieErrorWithStringAndUpdate(WeenieErrorWithString error, string message)
{
var fellowshipMembers = GetFellowshipMembers();
foreach (var member in fellowshipMembers.Values)
{
member.Session.Network.EnqueueSend(new GameEventWeenieErrorWithString(member.Session, error, message));
member.Session.Network.EnqueueSend(new GameEventFellowshipFullUpdate(member.Session));
}
}
public void QuitFellowship(Player player, bool disband)
{
if (player == null) return;
if (player.Guid.Full == FellowshipLeaderGuid)
{
if (disband)
{
var fellowshipMembers = GetFellowshipMembers();
foreach (var member in fellowshipMembers.Values)
{
member.Session.Network.EnqueueSend(new GameEventFellowshipDisband(member.Session));
if (ShareLoot)
{
member.Session.Network.EnqueueSend(new GameMessageSystemChat("You no longer have permission to loot anyone else's kills.", ChatMessageType.Broadcast));
// you would expect this occur, but it did not in retail pcaps
//foreach (var fellow in fellowshipMembers.Values)
// member.Session.Network.EnqueueSend(new GameMessageSystemChat($"{fellow.Name} does not have permission to loot your kills.", ChatMessageType.Broadcast));
}
member.Fellowship = null;
}
}
else
{
FellowshipMembers.Remove(player.Guid.Full);
if (IsLocked)
{
var timestamp = (int)Time.GetUnixTime();
if (!DepartedMembers.TryAdd(player.Guid.Full, timestamp))
DepartedMembers[player.Guid.Full] = timestamp;
}
player.Fellowship = null;
player.Session.Network.EnqueueSend(new GameEventFellowshipQuit(player.Session, player.Guid.Full));
player.Session.Network.EnqueueSend(new GameMessageSystemChat("You no longer have permission to loot anyone else's kills.", ChatMessageType.Broadcast));
var fellowshipMembers = GetFellowshipMembers();
foreach (var member in fellowshipMembers.Values)
{
member.Session.Network.EnqueueSend(new GameEventFellowshipQuit(member.Session, player.Guid.Full));
if (ShareLoot)
{
member.Session.Network.EnqueueSend(new GameMessageSystemChat($"You have lost permission to loot the kills of {player.Name}.", ChatMessageType.Broadcast));
player.Session.Network.EnqueueSend(new GameMessageSystemChat($"{member.Name} does not have permission to loot your kills.", ChatMessageType.Broadcast));
}
}
astignNewLeader(null, null);
CalculateXPSharing();
}
}
else if (!disband)
{
FellowshipMembers.Remove(player.Guid.Full);
if (IsLocked)
{
var timestamp = (int)Time.GetUnixTime();
if (!DepartedMembers.TryAdd(player.Guid.Full, timestamp))
DepartedMembers[player.Guid.Full] = timestamp;
}
player.Session.Network.EnqueueSend(new GameEventFellowshipQuit(player.Session, player.Guid.Full));
var fellowshipMembers = GetFellowshipMembers();
foreach (var member in fellowshipMembers.Values)
{
member.Session.Network.EnqueueSend(new GameEventFellowshipQuit(member.Session, player.Guid.Full));
if (ShareLoot)
{
member.Session.Network.EnqueueSend(new GameMessageSystemChat($"You have lost permission to loot the kills of {player.Name}.", ChatMessageType.Broadcast));
player.Session.Network.EnqueueSend(new GameMessageSystemChat($"{member.Name} does not have permission to loot your kills.", ChatMessageType.Broadcast));
}
}
player.Fellowship = null;
CalculateXPSharing();
}
}
public void astignNewLeader(Player oldLeader, Player newLeader)
{
if (newLeader != null)
{
FellowshipLeaderGuid = newLeader.Guid.Full;
if (oldLeader != null)
oldLeader.Session.Network.EnqueueSend(new GameEventWeenieErrorWithString(oldLeader.Session, WeenieErrorWithString.YouHavePastedFellowshipLeadershipTo_, newLeader.Name));
SendWeenieErrorWithStringAndUpdate(WeenieErrorWithString._IsNowLeaderOfFellowship, newLeader.Name);
}
else
{
// leader has dropped, astign new random leader
var fellowshipMembers = GetFellowshipMembers();
if (fellowshipMembers.Count == 0) return;
var rng = ThreadSafeRandom.Next(0, fellowshipMembers.Count - 1);
var fellowGuids = fellowshipMembers.Keys.ToList();
FellowshipLeaderGuid = fellowGuids[rng];
var newLeaderName = fellowshipMembers[FellowshipLeaderGuid].Name;
if (oldLeader != null)
oldLeader.Session.Network.EnqueueSend(new GameEventWeenieErrorWithString(oldLeader.Session, WeenieErrorWithString.YouHavePastedFellowshipLeadershipTo_, newLeaderName));
SendWeenieErrorWithStringAndUpdate(WeenieErrorWithString._IsNowLeaderOfFellowship, newLeaderName);
}
}
public void UpdateOpenness(bool isOpen)
{
Open = isOpen;
var openness = Open ? WeenieErrorWithString._IsNowOpenFellowship : WeenieErrorWithString._IsNowClosedFellowship;
SendWeenieErrorWithStringAndUpdate(openness, FellowshipName);
}
public void UpdateLock(bool isLocked, string lockName)
{
// Unlocking a fellowship is not possible without disbanding in retail worlds, so in all likelihood, this is only firing for fellowships being locked by emotemanager
IsLocked = isLocked;
if (string.IsNullOrWhiteSpace(lockName))
lockName = "Undefined";
if (isLocked)
{
Open = false;
DepartedMembers.Clear();
var timestamp = Time.GetUnixTime();
if (!FellowshipLocks.TryAdd(lockName, new FellowshipLockData(timestamp)))
FellowshipLocks[lockName].UpdateTimestamp(timestamp);
SendBroadcastAndUpdate("Your fellowship is now locked. You may not recruit new members. If you leave the fellowship, you have 15 minutes to be recruited back into the fellowship.");
}
else
{
// Unlocking a fellowship is not possible without disbanding in retail worlds, so in all likelihood, this never occurs
DepartedMembers.Clear();
FellowshipLocks.Remove(lockName);
SendBroadcastAndUpdate("Your fellowship is now unlocked.");
}
}
///
/// Calculates fellowship XP sharing (ShareXP, EvenShare) from fellow levels
///
private void CalculateXPSharing()
{
// - If all members of the fellowship are level 50 or above, all members will share XP equally
// - If all members of the fellowship are within 5 levels of the founder, XP will be shared equally
// - If members are all within ten levels of the founder, XP will be shared proportionally.
var fellows = GetFellowshipMembers();
var allEvenShareLevel = PropertyManager.GetLong("fellowship_even_share_level").Item;
var allOverEvenShareLevel = !fellows.Values.Any(f => (f.Level ?? 1) < allEvenShareLevel);
if (allOverEvenShareLevel)
{
ShareXP = DesiredShareXP;
EvenShare = true;
return;
}
var leader = PlayerManager.GetOnlinePlayer(FellowshipLeaderGuid);
if (leader == null)
return;
var maxLevelDiff = fellows.Values.Max(f => Math.Abs((leader.Level ?? 1) - (f.Level ?? 1)));
if (maxLevelDiff