C#:
using CompanionServer;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using Oxide.Core;
using Oxide.Core.Plugins;
using Oxide.Plugins;
using ProtoBuf;
using System;
using System.Collections;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using System.Text;
using System.Text.RegularExpressions;
using UnityEngine;
using VersionNumber = Oxide.Core.VersionNumber;
namespace Oxide.Plugins {
[Info("Clans", "k1lly0u", "3.0.31")]
class Clans : RustPlugin {
#region Fields
[PluginReference]
private Plugin DiscordClans;
internal StoredData storedData;
private bool wipeData;
private bool isInitialized = false;
private Coroutine initClansRoutine;
private Regex tagFilter;
private Regex hexFilter;
private int[] customTagMinValue;
private int[] customTagMaxValue;
private HashSet<ulong> friendlyFireDisabled = new HashSet<ulong>();
public static Clans Instance {
get;
private set;
}
private static DateTime Epoch = new DateTime(1970, 1, 1);
private static double MaxUnixSeconds = (DateTime.MaxValue - Epoch).TotalSeconds;
private const string COLORED_LABEL = "<color={0}>{1}</color>";
private enum MessageType {
Create, Invite, InviteReject, InviteWithdrawn, Join, Leave, Kick, Promote, Demote, Disband, AllianceInvite, AllianceInviteReject, AllianceInviteWithdrawn, AllianceAccept, AllianceWithdrawn, TeamChat, ClanChat, AllyChat
}
#endregion
#region Oxide Hooks
private void Loaded() {
Instance = this;
permission.RegisterPermission(configData.Permissions.PermissionCreate, this);
permission.RegisterPermission(configData.Permissions.PermissionJoin, this);
permission.RegisterPermission(configData.Permissions.PermissionLeave, this);
permission.RegisterPermission(configData.Permissions.PermissionDisband, this);
permission.RegisterPermission(configData.Permissions.PermissionKick, this);
lang.RegisterMessages(Messages, this);
cmd.AddChatCommand(configData.Commands.FFCommand, this, cmdClanFF);
cmd.AddChatCommand(configData.Commands.AFFCommand, this, cmdAllyFF);
cmd.AddChatCommand(configData.Commands.ClanCommand, this, cmdChatClan);
cmd.AddChatCommand(configData.Commands.AllyChatCommand, this, cmdAllianceChat);
cmd.AddChatCommand(configData.Commands.ClanChatCommand, this, cmdClanChat);
cmd.AddChatCommand(configData.Commands.ClanInfoCommand, this, cmdChatClanInfo);
cmd.AddChatCommand(configData.Commands.ClanHelpCommand, this, cmdChatClanHelp);
cmd.AddChatCommand(configData.Commands.ClanAllyCommand, this, cmdChatClanAlly);
_tags.Add(new KeyValuePair<string, string>(configData.Tags.TagOpen, configData.Tags.TagClose));
tagFilter = new Regex($"[^a-zA-Z0-9{configData.Tags.AllowedCharacters}]");
hexFilter = new Regex("^([A-Fa-f0-9]{6}|[A-Fa-f0-9]{3})$");
SetMinMaxColorRange();
if (!configData.Tags.EnabledBC) Unsubscribe(nameof(OnPluginLoaded));
if (!configData.Clans.MemberFF && !configData.Clans.Alliance.AllyFF) Unsubscribe(nameof(OnEntityTakeDamage));
if (configData.Clans.Teams.Enabled) RelationshipManager.maxTeamSize = configData.Clans.MemberLimit;
LoadData();
}
private void OnServerInitialized() {
initClansRoutine = ServerMgr.Instance.StartCoroutine(InitializeClans());
}
private void OnNewSave(string str) => wipeData = configData.Purge.WipeOnNewSave;
private void OnServerSave() => SaveData();
private void OnPluginLoaded(Plugin plugin) {
if (configData.Tags.EnabledBC && plugin?.Title == "Better Chat") Interface.CallHook("API_RegisterThirdPartyTitle", this, new Func<Oxide.Core.Libraries.Covalence.IPlayer, string>(BetterChat_FormattedClanTag));
}
private void OnPlayerConnected(BasePlayer player) {
if (player.currentTeam != 0UL) {
if (configData.Clans.Teams.Enabled) {
player.ClearTeam();
RelationshipManager.ServerInstance.playerToTeam.Remove(player.userID);
}
else {
RelationshipManager.PlayerTeam playerTeam = RelationshipManager.ServerInstance.FindTeam(player.currentTeam);
if (playerTeam == null || !playerTeam.members.Contains(player.userID)) {
player.ClearTeam();
RelationshipManager.ServerInstance.playerToTeam.Remove(player.userID);
}
}
}
Clan clan = storedData?.FindClanByID(player.userID);
if (clan != null) {
clan.OnPlayerConnected(player);
}
else {
List<string> invites;
if (storedData.playerInvites.TryGetValue(player.userID, out invites)) {
player.ChatMessage(string.Format(msg("Notification.PendingInvites", player.UserIDString), invites.ToSentence(), configData.Commands.ClanCommand));
}
}
}
private void OnPlayerDisconnected(BasePlayer player) => storedData?.FindClanByID(player.userID)?.OnPlayerDisconnected(player);
private object OnPlayerChat(BasePlayer player, string message, ConVar.Chat.ChatChannel channel) {
if (channel != ConVar.Chat.ChatChannel.Team) return null;
Clan clan = storedData.FindClanByID(player.userID);
if (clan == null) return null;
if (configData.Options.DenyOnMuted) {
object success = Interface.CallHook("API_IsMuted", player.IPlayer);
if ((success is bool && (bool)success) || player.HasPlayerFlag(BasePlayer.PlayerFlags.ChatMute)) {
player.ChatMessage(msg("Chat.IsMuted", player.UserIDString));
return false;
}
}
Instance.DiscordClans?.CallHook("LogMessage", $"{player.displayName} : {message}", (int)MessageType.TeamChat);
Interface.CallHook("OnClanChat", player, message, clan.Tag);
return null;
}
private object OnTeamCreate(BasePlayer player) {
if (!configData.Clans.Teams.Enabled) return null;
player.ChatMessage(msg("Notification.Create.NoNativeCreate", player.UserIDString));
return true;
}
private object OnTeamKick(RelationshipManager.PlayerTeam playerTeam, BasePlayer player, ulong targetId) {
if (!configData.Clans.Teams.Enabled || string.IsNullOrEmpty(playerTeam.teamName)) return null;
if (!configData.Clans.Teams.AllowKick) return false;
KickPlayer(player, targetId);
return true;
}
private object OnTeamInvite(BasePlayer player, BasePlayer other) {
if (!configData.Clans.Teams.Enabled) return null;
if (!configData.Clans.Teams.AllowInvite) return false;
if (other.IsNpc || other != null && !other.userID.IsSteamId()) return false;
InvitePlayer(player, other);
return true;
}
private object OnTeamAcceptInvite(RelationshipManager.PlayerTeam playerTeam, BasePlayer player) {
if (!configData.Clans.Teams.Enabled || string.IsNullOrEmpty(playerTeam.teamName)) return null;
JoinClan(player, playerTeam.teamName);
return true;
}
private object OnTeamRejectInvite(BasePlayer player, RelationshipManager.PlayerTeam playerTeam) {
if (!configData.Clans.Teams.Enabled || string.IsNullOrEmpty(playerTeam.teamName)) return null;
RejectInvite(player, playerTeam.teamName);
return true;
}
private object OnTeamLeave(RelationshipManager.PlayerTeam playerTeam, BasePlayer player) {
if (!configData.Clans.Teams.Enabled || string.IsNullOrEmpty(playerTeam.teamName)) return null;
if (!configData.Clans.Teams.AllowLeave) return false;
LeaveClan(player);
return true;
}
private object OnTeamPromote(RelationshipManager.PlayerTeam playerTeam, BasePlayer player) {
if (!configData.Clans.Teams.Enabled || string.IsNullOrEmpty(playerTeam.teamName)) return null;
if (!configData.Clans.Teams.AllowPromote) return false;
PromotePlayer(BasePlayer.FindByID(playerTeam.teamLeader), player.userID);
return true;
}
private object OnTeamDisband(RelationshipManager.PlayerTeam playerTeam) {
if (!configData.Clans.Teams.Enabled || string.IsNullOrEmpty(playerTeam.teamName)) return null;
Clan clan = storedData.FindClan(playerTeam.teamName);
if (clan != null) clan.DisbandClan();
return true;
}
private object OnEntityTakeDamage(BasePlayer player, HitInfo info) {
if (player == null || info?.InitiatorPlayer == null) return null;
if (player == info.InitiatorPlayer) return null;
Clan victimClan = storedData.FindClanByID(player.userID);
if (victimClan == null) return null;
Clan attackerClan = storedData.FindClanByID(info.InitiatorPlayer.userID);
if (attackerClan == null) return null;
Clan.Member member = storedData.FindMemberByID(info.InitiatorPlayer.userID);
if (member == null) return null;
if (friendlyFireDisabled.Contains(player.userID)) return null;
if (victimClan.Tag.Equals(attackerClan.Tag) && configData.Clans.MemberFF && member.MemberFFEnabled) {
member.OnClanMemberHit(string.Format(COLORED_LABEL, victimClan.GetRoleColor(player.userID), player.displayName));
return true;
}
if (victimClan.IsAlliedClan(attackerClan.Tag) && configData.Clans.Alliance.Enabled && configData.Clans.Alliance.AllyFF && member.AllyFFEnabled) {
member.OnAllyMemberHit(string.Format(COLORED_LABEL, victimClan.GetRoleColor(player.userID), player.displayName));
return true;
}
return null;
}
private void Unload() {
if (isInitialized) {
SaveData();
foreach (Clan clan in storedData.clans.Values) clan.OnUnload();
}
else {
if (initClansRoutine != null) ServerMgr.Instance.StopCoroutine(initClansRoutine);
}
configData = null;
Instance = null;
}
#endregion
#region Functions
private IEnumerator InitializeClans() {
Puts("Initializing Clans...");
if (configData.Clans.Teams.Enabled) {
RelationshipManager.ServerInstance.playerToTeam.Clear();
foreach(KeyValuePair<ulong, RelationshipManager.PlayerTeam> kvp in RelationshipManager.ServerInstance.teams) {
RelationshipManager.PlayerTeam playerTeam = kvp.Value;
ClearTeam(ref playerTeam);
}
RelationshipManager.ServerInstance.teams.Clear();
RelationshipManager.ServerInstance.lastTeamIndex = 1;
}
if (wipeData) {
storedData.clans.Clear();
storedData.playerInvites.Clear();
SaveData();
}
else {
List<string> purgedClans = Facepunch.Pool.GetList<string>();
foreach (KeyValuePair<string, Clan> kvp in storedData.clans) {
Clan clan = kvp.Value;
if (clan.ClanMembers.Count == 0 || (configData.Purge.Enabled && UnixTimeStampUTC() - clan.LastOnlineTime > (configData.Purge.OlderThanDays * 86400))) {
purgedClans.Add(kvp.Key);
continue;
}
clan.Description = StripHTMLTags(clan.Description);
if (configData.Clans.Alliance.Enabled) {
for (int i = clan.AllianceInvites.Count - 1; i >= 0; i--) {
KeyValuePair<string, double> allianceInvite = clan.AllianceInvites.ElementAt(i);
if (!storedData.clans.ContainsKey(allianceInvite.Key) || (UnixTimeStampUTC() - allianceInvite.Value > configData.Clans.Invites.AllianceInviteExpireTime)) clan.AllianceInvites.Remove(allianceInvite.Key);
}
for (int i = clan.Alliances.Count - 1; i >= 0; i--) {
string allyTag = clan.Alliances.ElementAt(i);
if (!storedData.clans.ContainsKey(allyTag)) clan.Alliances.Remove(allyTag);
}
}
for (int i = clan.MemberInvites.Count - 1; i >= 0; i--) {
KeyValuePair<ulong, Clan.MemberInvite> memberInvite = clan.MemberInvites.ElementAt(i);
if (UnixTimeStampUTC() - memberInvite.Value.ExpiryTime > configData.Clans.Invites.MemberInviteExpireTime) clan.MemberInvites.Remove(memberInvite.Key);
}
foreach (KeyValuePair<ulong, Clan.Member> member in clan.ClanMembers) storedData.RegisterPlayer(member.Key, clan.Tag);
if (configData.Permissions.PermissionGroups) permission.CreateGroup(configData.Permissions.PermissionGroupPrefix + clan.Tag, "Clan " + clan.Tag, 0);
yield return null;
}
if (purgedClans.Count > 0) {
Puts($"Purging {purgedClans.Count} expired or invalid clans");
StringBuilder str = new StringBuilder();
for (int i = 0; i < purgedClans.Count; i++) {
string tag = purgedClans[i];
Clan clan = storedData.clans[tag];
if (clan == null) continue;
permission.RemoveGroup(configData.Permissions.PermissionGroupPrefix + clan.Tag);
str.Append($"{(i > 0 ? "\n" : "")}Purged - [{tag}] | {clan.Description} | Owner: {clan.OwnerID} | Last Online: {UnixTimeStampToDateTime(clan.LastOnlineTime)}");
storedData.clans.Remove(tag);
}
if (configData.Purge.ListPurgedClans) {
Puts(str.ToString());
if (configData.Options.LogChanges) LogToFile(Title, str.ToString(), this);
}
}
Puts($"Loaded {storedData.clans.Count} clans!");
Facepunch.Pool.FreeList(ref purgedClans);
}
if (configData.Tags.EnabledBC) Interface.CallHook("API_RegisterThirdPartyTitle", this, new Func<Oxide.Core.Libraries.Covalence.IPlayer, string>(BetterChat_FormattedClanTag));
isInitialized = true;
initClansRoutine = null;
foreach (BasePlayer player in BasePlayer.activePlayerList) OnPlayerConnected(player);
}
#endregion
#region Clan Tag Colors
private void SetMinMaxColorRange() {
customTagMinValue = new int[3];
customTagMaxValue = new int[3];
if (configData.Tags.CustomTagColorMin.StartsWith("#")) configData.Tags.CustomTagColorMin = configData.Tags.CustomTagColorMin.Substring(1);
if (configData.Tags.CustomTagColorMax.StartsWith("#")) configData.Tags.CustomTagColorMax = configData.Tags.CustomTagColorMax.Substring(1);
customTagMinValue[0] = int.Parse(configData.Tags.CustomTagColorMin.Substring(0, 2), NumberStyles.AllowHexSpecifier);
customTagMinValue[1] = int.Parse(configData.Tags.CustomTagColorMin.Substring(2, 2), NumberStyles.AllowHexSpecifier);
customTagMinValue[2] = int.Parse(configData.Tags.CustomTagColorMin.Substring(3, 2), NumberStyles.AllowHexSpecifier);
customTagMaxValue[0] = int.Parse(configData.Tags.CustomTagColorMax.Substring(0, 2), NumberStyles.AllowHexSpecifier);
customTagMaxValue[1] = int.Parse(configData.Tags.CustomTagColorMax.Substring(2, 2), NumberStyles.AllowHexSpecifier);
customTagMaxValue[2] = int.Parse(configData.Tags.CustomTagColorMax.Substring(3, 2), NumberStyles.AllowHexSpecifier);
}
private bool TagColorIsBlocked(string color) {
if (color.StartsWith("#")) color = color.Substring(1);
if (configData.Tags.BlockedTagColors.Contains(color, TagEqualityComparer.Instance)) return true;
return false;
}
private bool TagColorWithinRange(string color) {
if (color.StartsWith("#")) color = color.Substring(1);
int red = int.Parse(color.Substring(0, 2), NumberStyles.AllowHexSpecifier);
int green = int.Parse(color.Substring(2, 2), NumberStyles.AllowHexSpecifier);
int blue = int.Parse(color.Substring(4, 2), NumberStyles.AllowHexSpecifier);
return red >= customTagMinValue[0] && red <= customTagMaxValue[0] && green >= customTagMinValue[1] && green <= customTagMaxValue[1] && blue >= customTagMinValue[2] && blue <= customTagMaxValue[2];
}
public class TagEqualityComparer : IEqualityComparer<string> {
private static TagEqualityComparer _instance;
public static TagEqualityComparer Instance {
get {
if (_instance == null) _instance = new TagEqualityComparer();
return _instance;
}
}
public bool Equals(string a, string b) {
return a.Equals(b, StringComparison.OrdinalIgnoreCase);
}
public int GetHashCode(string obj) {
return obj.GetHashCode();
}
}
#endregion
#region Helpers
private string BetterChat_FormattedClanTag(Oxide.Core.Libraries.Covalence.IPlayer player) {
Clan clan = storedData.FindClanByID(player.Id);
if (clan == null) return string.Empty;
string tagColor = string.IsNullOrEmpty(clan.TagColor) || !configData.Tags.CustomColors ? configData.Tags.TagColor : clan.TagColor;
if (configData.Tags.EnabledGroupColors && (string.IsNullOrEmpty(clan.TagColor) || !configData.Tags.CustomColors)) {
foreach (KeyValuePair<string, string> kvp in configData.Tags.GroupTagColors) {
if (player.BelongsToGroup(kvp.Key)) {
tagColor = kvp.Value;
break;
}
}
}
return $"[#{tagColor.Replace("#", "")}][+{configData.Tags.TagSize}]{configData.Tags.TagOpen}{clan.Tag}{configData.Tags.TagClose}[/+][/#]";
}
private static int UnixTimeStampUTC() => (int)DateTime.UtcNow.Subtract(Epoch).TotalSeconds;
private static DateTime UnixTimeStampToDateTime(double unixTimeStamp) {
return unixTimeStamp > MaxUnixSeconds ? Epoch.AddMilliseconds(unixTimeStamp) : Epoch.AddSeconds(unixTimeStamp);
}
private bool ContainsBlockedWord(string tag) {
for (int i = 0; i < configData.Tags.BlockedWords.Length; i++) {
if (TranslateLeet(tag).ToLower().Contains(configData.Tags.BlockedWords[i].ToLower())) return true;
}
return false;
}
private string TranslateLeet(string original) {
string translated = original;
foreach (KeyValuePair<string, string> leet in leetTable) translated = translated.Replace(leet.Key, leet.Value);
return translated;
}
private bool ClanTagExists(string tag) {
ICollection<string> collection = storedData.clans.Keys;
for (int i = 0; i < collection.Count; i++) {
if (collection.ElementAt(i).Equals(tag, StringComparison.OrdinalIgnoreCase)) return true;
}
return false;
}
private string FormatTime(double time) {
TimeSpan dateDifference = TimeSpan.FromSeconds((float)time);
int days = dateDifference.Days;
int hours = dateDifference.Hours + (days * 24);
int mins = dateDifference.Minutes;
int secs = dateDifference.Seconds;
if (hours > 0) return string.Format("{0:00}h:{1:00}m:{2:00}s", hours, mins, secs);
else if (mins > 0) return string.Format("{0:00}m:{1:00}s", mins, secs);
else return string.Format("{0:00}s", secs);
}
private BasePlayer FindPlayer(string partialNameOrID) => BasePlayer.allPlayerList.FirstOrDefault<BasePlayer>((BasePlayer x) => x.displayName.Equals(partialNameOrID, StringComparison.OrdinalIgnoreCase)) ?? BasePlayer.allPlayerList.FirstOrDefault<BasePlayer>((BasePlayer x) => x.displayName.Contains(partialNameOrID, CompareOptions.OrdinalIgnoreCase)) ?? BasePlayer.allPlayerList.FirstOrDefault<BasePlayer>((BasePlayer x) => x.UserIDString == partialNameOrID);
private static void RemoveFromTeam(RelationshipManager.PlayerTeam playerTeam, BasePlayer player) {
playerTeam.members.Remove(player.userID);
RelationshipManager.ServerInstance.playerToTeam.Remove(player.userID);
player.ClearTeam();
}
private static void RemoveFromTeam(RelationshipManager.PlayerTeam playerTeam, ulong playerId) {
playerTeam.members.Remove(playerId);
RelationshipManager.ServerInstance.playerToTeam.Remove(playerId);
RelationshipManager.FindByID(playerId)?.ClearTeam();
}
private static void ClearTeam(ref RelationshipManager.PlayerTeam playerTeam) {
playerTeam.invites.Clear();
playerTeam.members.Clear();
playerTeam.onlineMemberConnections.Clear();
playerTeam.teamID = 0UL;
playerTeam.teamLeader = 0UL;
playerTeam.teamName = string.Empty;
Facepunch.Pool.Free(ref playerTeam);
}
private static string RemoveTags(string str) {
foreach (KeyValuePair<string, string> kvp in _tags) {
if (str.StartsWith(kvp.Key) && str.Contains(kvp.Value) && str.Length > str.IndexOf(kvp.Value)) {
str = str.Substring(str.IndexOf(kvp.Value) + 1).Trim();
}
}
return str;
}
private static List<KeyValuePair<string, string>> _tags = new List<KeyValuePair<string, string>> {
new KeyValuePair<string, string>("[", "]"), new KeyValuePair<string, string>("{", "}"), new KeyValuePair<string, string>("(", ")"), new KeyValuePair<string, string>("<", ">"),
}
;
public static string StripHTMLTags(string source) {
char[] array = new char[source.Length];
int arrayIndex = 0;
bool inside = false;
for (int i = 0; i < source.Length; i++) {
char let = source[i];
if (let == '<') {
inside = true;
continue;
}
if (let == '>') {
inside = false;
continue;
}
if (!inside) {
array[arrayIndex] = let;
arrayIndex++;
}
}
return new string(array, 0, arrayIndex);
}
#endregion
#region Clan Management
internal void CreateClan(BasePlayer player, string tag, string description) {
if (player == null) return;
if (storedData.FindClanByID(player.userID) != null) {
player.ChatMessage(msg("Notification.Create.InExistingClan", player.UserIDString));
return;
}
if (configData.Permissions.UsePermissionCreate && !permission.UserHasPermission(player.UserIDString, configData.Permissions.PermissionCreate)) {
player.ChatMessage(msg("Notification.Create.NoPermission", player.UserIDString));
return;
}
List<ulong> list;
if (configData.Tags.ReservedClanTags.TryGetValue(tag, out list)) {
if (!list.Contains(player.userID)) {
player.ChatMessage(string.Format(msg("Notification.Create.TagReserved1", player.UserIDString), tag));
return;
}
}
if (tag.Length < configData.Tags.TagLength.Minimum || tag.Length > configData.Tags.TagLength.Maximum) {
player.ChatMessage(string.Format(msg("Notification.Create.InvalidTagLength", player.UserIDString), configData.Tags.TagLength.Minimum, configData.Tags.TagLength.Maximum));
return;
}
if (tagFilter.IsMatch(tag) || ContainsBlockedWord(tag)) {
player.ChatMessage(msg("Notification.Create.InvalidCharacters", player.UserIDString));
return;
}
if (ClanTagExists(tag)) {
player.ChatMessage(msg("Notification.Create.ClanExists", player.UserIDString));
return;
}
if (configData.Clans.Teams.Enabled && player.currentTeam != 0UL) {
RelationshipManager.PlayerTeam playerTeam = RelationshipManager.ServerInstance.FindTeam(player.currentTeam);
if (playerTeam != null) RemoveFromTeam(playerTeam, player);
RelationshipManager.ServerInstance.playerToTeam.Remove(player.userID);
player.ClearTeam();
}
storedData.clans[tag] = new Clan(player, tag, description);
storedData.RegisterPlayer(player.userID, tag);
player.ChatMessage(string.Format(msg("Notification.Create.Success", player.UserIDString), tag));
Interface.CallHook("OnClanCreate", tag);
if (configData.Options.LogChanges) LogToFile(Title, $"{player.displayName} created the clan [{tag}]", this);
DiscordClans?.CallHook("LogMessage", $"{player.displayName} has created the clan {tag}", (int)MessageType.Create);
}
internal bool InvitePlayer(BasePlayer inviter, ulong targetId) {
BasePlayer invitee = (covalence.Players.FindPlayerById(targetId.ToString())?.Object as BasePlayer) ?? null;
if (invitee == null) {
inviter.ChatMessage(string.Format(msg("Notification.Generic.UnableToFindPlayer", inviter.UserIDString), targetId));
return false;
}
return InvitePlayer(inviter, invitee);
}
internal bool InvitePlayer(BasePlayer inviter, BasePlayer invitee) {
if (inviter == null || invitee == null) return false;
if (invitee.IsNpc || !invitee.userID.IsSteamId()) return false;
Clan clan = storedData.FindClanByID(inviter.userID);
if (clan == null) {
inviter.ChatMessage(msg("Notification.Generic.NoClan", inviter.UserIDString));
return false;
}
Clan other = storedData.FindClanByID(invitee.userID);
if (other != null) {
inviter.ChatMessage(string.Format(msg("Notification.Invite.InClan", inviter.UserIDString), invitee.displayName));
return false;
}
if (configData.Permissions.UsePermissionJoin && !permission.UserHasPermission(invitee.UserIDString, configData.Permissions.PermissionJoin)) {
inviter.ChatMessage(msg("Notification.Invite.NoPermission", inviter.UserIDString));
return false;
}
return clan.InvitePlayer(inviter, invitee);
}
internal bool WithdrawInvite(BasePlayer player, string partialNameOrID) {
if (player == null) return false;
Clan clan = storedData.FindClanByID(player.userID);
if (clan == null) {
player.ChatMessage(msg("Notification.Generic.NoClan", player.UserIDString));
return false;
}
if (!clan.IsOwner(player.userID) && !clan.IsModerator(player.userID) && !clan.IsCouncil(player.userID)) {
player.ChatMessage(msg("Notification.WithdrawInvite.NoPermissions", player.UserIDString));
return false;
}
ulong targetId;
if (!ulong.TryParse(partialNameOrID, out targetId)) targetId = 0UL;
foreach(KeyValuePair<ulong, Clan.MemberInvite> invite in clan.MemberInvites) {
if ((targetId != 0UL && targetId.Equals(invite.Key)) || invite.Value.DisplayName.Contains(partialNameOrID)) {
storedData.RevokePlayerInvite(targetId, clan.Tag);
clan.MemberInvites.Remove(invite.Key);
clan.Broadcast("Notification.WithdrawInvite.Success", player.displayName, invite.Value.DisplayName);
DiscordClans?.CallHook("LogMessage", $"{player.displayName} has withdrawn a clan invite for {invite.Value.DisplayName}", (int)MessageType.InviteWithdrawn);
return true;
}
}
if (configData.Clans.Teams.Enabled) player.ClearPendingInvite();
player.ChatMessage(string.Format(msg("Notification.WithdrawInvite.UnableToFind", player.UserIDString), partialNameOrID));
return false;
}
internal bool RejectInvite(BasePlayer player, string tag) {
if (player == null) return false;
Clan clan = storedData.FindClan(tag);
if (clan == null) {
player.ChatMessage(string.Format(msg("Notification.Generic.InvalidClan", player.UserIDString), tag));
return false;
}
if (!clan.MemberInvites.ContainsKey(player.userID)) {
player.ChatMessage(string.Format(msg("Notification.RejectInvite.InvalidInvite", player.UserIDString), tag));
return false;
}
clan.MemberInvites.Remove(player.userID);
storedData.OnInviteRejected(player.userID, clan.Tag);
if (configData.Clans.Teams.Enabled) clan.PlayerTeam.RejectInvite(player);
clan.Broadcast("Notification.RejectInvite.Message", player.displayName);
player.ChatMessage(string.Format(msg("Notification.RejectInvite.PlayerMessage", player.UserIDString), tag));
if (configData.Options.LogChanges) Instance.LogToFile(Instance.Title, $"{player.displayName} rejected their invite to [{tag}]", Instance);
return true;
}
internal bool JoinClan(BasePlayer player, string tag) {
if (player == null || string.IsNullOrEmpty(tag)) return false;
if (configData.Permissions.UsePermissionJoin && !permission.UserHasPermission(player.UserIDString, configData.Permissions.PermissionJoin)) {
player.ChatMessage(msg("Notification.Join.NoPermission", player.UserIDString));
return false;
}
Clan clan = storedData.FindClanByID(player.userID);
if (clan != null) {
player.ChatMessage(msg("Notification.Join.InExistingClan", player.UserIDString));
return false;
}
clan = storedData.FindClan(tag);
if (clan == null) return false;
return clan.JoinClan(player);
}
internal bool LeaveClan(BasePlayer player) {
if (player == null) return false;
Clan clan = storedData.FindClanByID(player.userID);
if (clan == null) {
player.ChatMessage(msg("Notification.Generic.NoClan", player.UserIDString));
return false;
}
if (configData.Permissions.UsePermissionLeave && !permission.UserHasPermission(player.UserIDString, configData.Permissions.PermissionLeave)) {
player.ChatMessage(msg("Notification.Leave.NoPermission", player.UserIDString));
return false;
}
return clan.LeaveClan(player);
}
internal bool KickPlayer(BasePlayer player, ulong playerId) {
if (player == null) return false;
Clan clan = storedData.FindClanByID(player.userID);
if (clan == null) {
player.ChatMessage(msg("Notification.Generic.NoClan", player.UserIDString));
return false;
}
if (configData.Permissions.UsePermissionKick && !permission.UserHasPermission(player.UserIDString, configData.Permissions.PermissionKick)) {
player.ChatMessage(msg("Notification.Kick.NoPermission", player.UserIDString));
return false;
}
return clan.KickMember(player, playerId);
}
internal bool PromotePlayer(BasePlayer promoter, ulong targetId) {
if (promoter == null) return false;
Clan clan = storedData.FindClanByID(promoter.userID);
if (clan == null) {
promoter.ChatMessage(msg("Notification.Generic.NoClan", promoter.UserIDString));
return false;
}
Clan other = storedData.FindClanByID(targetId);
if (other == null || !clan.Tag.Equals(other.Tag)) {
string displayName = covalence.Players.FindPlayer(targetId.ToString())?.Name ?? targetId.ToString();
promoter.ChatMessage(string.Format(msg("Notification.Promotion.TargetNoClan", promoter.UserIDString), displayName));
return false;
}
return clan.PromotePlayer(promoter, targetId);
}
internal bool DemotePlayer(BasePlayer demoter, ulong targetId) {
if (demoter == null) return false;
Clan clan = storedData.FindClanByID(demoter.userID);
if (clan == null) {
demoter.ChatMessage(msg("Notification.Generic.NoClan", demoter.UserIDString));
return false;
}
Clan other = storedData.FindClanByID(targetId);
if (other == null || !clan.Tag.Equals(other.Tag)) {
string displayName = covalence.Players.FindPlayer(targetId.ToString())?.Name ?? targetId.ToString();
demoter.ChatMessage(string.Format(msg("Notification.Promotion.TargetNoClan", demoter.UserIDString), displayName));
return false;
}
return clan.DemotePlayer(demoter, targetId);
}
internal bool DisbandClan(BasePlayer player) {
Clan clan = storedData.FindClanByID(player.userID);
if (clan == null) {
player.ChatMessage(msg("Notification.Generic.NoClan", player.UserIDString));
return false;
}
if (!clan.IsOwner(player.userID)) {
player.ChatMessage(msg("Notification.Disband.NotOwner", player.UserIDString));
return false;
}
if (configData.Permissions.UsePermissionDisband && !permission.UserHasPermission(player.UserIDString, configData.Permissions.PermissionDisband)) {
player.ChatMessage(msg("Notification.Disband.NoPermission", player.UserIDString));
return false;
}
string tag = clan.Tag;
clan.Broadcast("Notification.Disband.Message", Array.Empty<object>());
clan.DisbandClan();
player.ChatMessage(string.Format(msg("Notification.Disband.Success", player.UserIDString), tag));
return true;
}
#endregion
#region Alliance Management
internal bool OfferAlliance(BasePlayer player, string tag) {
if (!configData.Clans.Alliance.Enabled) return false;
Clan clan = storedData.FindClanByID(player.userID);
if (clan == null) {
player.ChatMessage(msg("Notification.Generic.NoClan", player.UserIDString));
return false;
}
Clan alliedClan = storedData.FindClan(tag);
if (alliedClan == null) {
player.ChatMessage(string.Format(msg("Notification.Generic.InvalidClan", player.UserIDString), tag));
return false;
}
if (!clan.IsOwner(player.userID) && !clan.IsCouncil(player.userID)) {
player.ChatMessage(msg("Notification.Alliance.NoPermissions", player.UserIDString));
return false;
}
if (clan.AllianceInvites.ContainsKey(tag) && (UnixTimeStampUTC() - clan.AllianceInvites[tag] < configData.Clans.Invites.AllianceInviteExpireTime)) {
player.ChatMessage(string.Format(msg("Notification.Alliance.PendingInvite", player.UserIDString), tag));
return false;
}
if (clan.AllianceInviteCount >= configData.Clans.Invites.AllianceInviteLimit) {
player.ChatMessage(msg("Notification.Alliance.MaximumInvites", player.UserIDString));
return false;
}
if (clan.AllianceCount >= configData.Clans.Alliance.AllianceLimit) {
player.ChatMessage(msg("Notification.Alliance.MaximumAlliances", player.UserIDString));
return false;
}
if (configData.Clans.Alliance.CountAllianceMembers) {
int count = clan.CountMembersAndAlliances();
if (count + alliedClan.MemberCount >= configData.Clans.MemberLimit) {
player.ChatMessage(msg("Notification.Alliance.AtLimitAlliedMembersSelf", player.UserIDString));
return false;
}
count = alliedClan.CountMembersAndAlliances();
if (count + clan.MemberCount >= configData.Clans.MemberLimit) {
player.ChatMessage(string.Format(msg("Notification.Alliance.AtLimitAlliedMembersTarget", player.UserIDString), alliedClan.Tag));
return false;
}
}
clan.AllianceInvites[tag] = UnixTimeStampUTC();
alliedClan.IncomingAlliances.Add(clan.Tag);
player.ChatMessage(string.Format(msg("Notification.Alliance.InviteSent", player.UserIDString), tag, FormatTime(configData.Clans.Invites.AllianceInviteExpireTime)));
alliedClan.Broadcast("Notification.Alliance.InviteReceived", clan.Tag, FormatTime(configData.Clans.Invites.AllianceInviteExpireTime), configData.Commands.ClanAllyCommand);
Instance.DiscordClans?.CallHook("LogMessage", $"{player.displayName} has offered an alliance to [{tag}]", (int)MessageType.AllianceInvite);
return true;
}
internal bool WithdrawAlliance(BasePlayer player, string tag) {
if (!configData.Clans.Alliance.Enabled) return false;
Clan clan = storedData.FindClanByID(player.userID);
if (clan == null) {
player.ChatMessage(msg("Notification.Generic.NoClan", player.UserIDString));
return false;
}
Clan alliedClan = storedData.FindClan(tag);
if (alliedClan == null) {
player.ChatMessage(string.Format(msg("Notification.Generic.InvalidClan", player.UserIDString), tag));
return false;
}
if (!clan.IsOwner(player.userID) && !clan.IsCouncil(player.userID)) {
player.ChatMessage(msg("Notification.Alliance.NoPermissions", player.UserIDString));
return false;
}
if (!clan.AllianceInvites.ContainsKey(tag)) {
player.ChatMessage(string.Format(msg("Notification.Alliance.NoActiveInvite", player.UserIDString), tag));
return false;
}
clan.AllianceInvites.Remove(tag);
alliedClan.IncomingAlliances.Remove(clan.Tag);
clan.Broadcast("Notification.Alliance.WithdrawnClan", player.displayName, tag);
alliedClan.Broadcast("Notification.Alliance.WithdrawnTarget", clan.Tag);
clan.MarkDirty();
Instance.DiscordClans?.CallHook("LogMessage", $"{player.displayName} has withdrawn their alliance offer to [{tag}]", (int)MessageType.AllianceInviteWithdrawn);
return true;
}
internal bool AcceptAlliance(BasePlayer player, string tag) {
if (!configData.Clans.Alliance.Enabled) return false;
Clan clan = storedData.FindClanByID(player.userID);
if (clan == null) {
player.ChatMessage(msg("Notification.Generic.NoClan", player.UserIDString));
return false;
}
Clan alliedClan = storedData.FindClan(tag);
if (alliedClan == null) {
player.ChatMessage(string.Format(msg("Notification.Generic.InvalidClan", player.UserIDString), tag));
return false;
}
if (!clan.IsOwner(player.userID) && !clan.IsCouncil(player.userID)) {
player.ChatMessage(msg("Notification.Alliance.NoPermissions", player.UserIDString));
return false;
}
bool noActiveInvite = !alliedClan.AllianceInvites.ContainsKey(clan.Tag);
if ((UnixTimeStampUTC() - alliedClan.AllianceInvites[clan.Tag] > configData.Clans.Invites.AllianceInviteExpireTime)) {
alliedClan.AllianceInvites.Remove(clan.Tag);
noActiveInvite = true;
}
if (noActiveInvite) {
player.ChatMessage(string.Format(msg("Notification.Alliance.NoActiveInviteFrom", player.UserIDString), tag));
return false;
}
if (alliedClan.AllianceCount >= configData.Clans.Alliance.AllianceLimit) {
player.ChatMessage(string.Format(msg("Notification.Alliance.AtLimitTarget", player.UserIDString), tag));
return false;
}
if (clan.AllianceCount >= configData.Clans.Alliance.AllianceLimit) {
player.ChatMessage(string.Format(msg("Notification.Alliance.AtLimitSelf", player.UserIDString), tag));
return false;
}
if (configData.Clans.Alliance.CountAllianceMembers) {
int count = clan.CountMembersAndAlliances();
if (count + alliedClan.MemberCount >= configData.Clans.MemberLimit) {
player.ChatMessage(msg("Notification.Alliance.AtLimitAlliedMembersSelf", player.UserIDString));
return false;
}
count = alliedClan.CountMembersAndAlliances();
if (count + clan.MemberCount >= configData.Clans.MemberLimit) {
player.ChatMessage(string.Format(msg("Notification.Alliance.AtLimitAlliedMembersTarget", player.UserIDString), alliedClan.Tag));
return false;
}
}
clan.Alliances.Add(tag);
clan.IncomingAlliances.Remove(tag);
alliedClan.Alliances.Add(clan.Tag);
alliedClan.AllianceInvites.Remove(clan.Tag);
clan.MarkDirty();
alliedClan.MarkDirty();
clan.Broadcast("Notification.Alliance.Formed", clan.Tag, alliedClan.Tag);
alliedClan.Broadcast("Notification.Alliance.Formed", clan.Tag, alliedClan.Tag);
Interface.Oxide.CallHook("OnClanUpdate", clan.Tag);
Interface.Oxide.CallHook("OnClanUpdate", alliedClan.Tag);
Instance.DiscordClans?.CallHook("LogMessage", $"{player.displayName} has accepted a alliance offer from [{tag}]", (int)MessageType.AllianceAccept);
return true;
}
internal bool RejectAlliance(BasePlayer player, string tag) {
if (!configData.Clans.Alliance.Enabled) return false;
Clan clan = storedData.FindClanByID(player.userID);
if (clan == null) {
player.ChatMessage(string.Format(msg("Notification.Generic.InvalidClan", player.UserIDString), tag));
return false;
}
Clan alliedClan = storedData.FindClan(tag);
if (alliedClan == null) {
player.ChatMessage(msg("Notification.Generic.InvalidClan", player.UserIDString));
return false;
}
if (!clan.IsOwner(player.userID) && !clan.IsCouncil(player.userID)) {
player.ChatMessage(msg("Notification.Alliance.NoPermissions", player.UserIDString));
return false;
}
if (!alliedClan.AllianceInvites.ContainsKey(clan.Tag) || (UnixTimeStampUTC() - alliedClan.AllianceInvites[clan.Tag] > configData.Clans.Invites.AllianceInviteExpireTime)) {
player.ChatMessage(string.Format(msg("Notification.Alliance.NoActiveInvite", player.UserIDString), tag));
return false;
}
clan.IncomingAlliances.Remove(tag);
alliedClan.AllianceInvites.Remove(clan.Tag);
alliedClan.MarkDirty();
clan.Broadcast("Notification.Alliance.Rejected", clan.Tag, alliedClan.Tag);
alliedClan.Broadcast("Notification.Alliance.Rejected", clan.Tag, alliedClan.Tag);
Instance.DiscordClans?.CallHook("LogMessage", $"{player.displayName} has rejected a alliance offer from [{tag}]", (int)MessageType.AllianceInviteReject);
return true;
}
internal bool RevokeAlliance(BasePlayer player, string tag) {
if (!configData.Clans.Alliance.Enabled) return false;
Clan clan = storedData.FindClanByID(player.userID);
if (clan == null) {
player.ChatMessage(string.Format(msg("Notification.Generic.InvalidClan", player.UserIDString), tag));
return false;
}
Clan alliedClan = storedData.FindClan(tag);
if (alliedClan == null) {
player.ChatMessage(msg("Notification.Generic.InvalidClan", player.UserIDString));
return false;
}
if (!clan.IsOwner(player.userID) && !clan.IsCouncil(player.userID)) {
player.ChatMessage(msg("Notification.Alliance.NoPermissions", player.UserIDString));
return false;
}
if (!clan.Alliances.Contains(alliedClan.Tag)) {
player.ChatMessage(string.Format(msg("Notification.Alliance.NoActiveAlliance", player.UserIDString), alliedClan.Tag));
return false;
}
alliedClan.Alliances.Remove(clan.Tag);
clan.Alliances.Remove(alliedClan.Tag);
alliedClan.MarkDirty();
clan.MarkDirty();
clan.Broadcast("Notification.Alliance.Revoked", clan.Tag, alliedClan.Tag);
alliedClan.Broadcast("Notification.Alliance.Revoked", clan.Tag, alliedClan.Tag);
Instance.DiscordClans?.CallHook("LogMessage", $"{player.displayName} has withdrawn their alliance to [{tag}]", (int)MessageType.AllianceWithdrawn);
return true;
}
#endregion
#region Chat
private void ClanChat(BasePlayer player, string message) {
// ... [Code preview truncated due to length. Full source in attached file] ...