C#:
// Все права принадлежат дискорд сообществу https://discord.gg/VgNHPpNrz6
/* Copyright (C) Whispers88 - All Rights Reserved
* Unauthorized copying of this file, via any medium is strictly prohibited
* Proprietary and confidential
* Written by Whispers88 rustafarian.server@gmail.com, Feb 2025
* Version 1.1.3
*
*You may not copy, modify, merge, publish, distribute, sublicense, or sell copies of This Software without the Developer’s consent
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS
BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE
GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.*/
using Newtonsoft.Json;
using Oxide.Core;
using Oxide.Core.Configuration;
using Oxide.Core.Libraries.Covalence;
using Rust;
using System;
using System.Collections.Generic;
using UnityEngine;
using Facepunch;
using System.Collections;
using UnityEngine.Networking;
using Oxide.Core.Plugins;
using System.Text;
using HarmonyLib;
using System.Linq;
using System.IO;
using System.Collections.Concurrent;
using ProtoBuf;
namespace Oxide.Plugins {
[Info("Team Tracker", "Whispers88", "1.1.3")]
[Description("Tracks player data to identify associated players")]
public class TeamTracker : CovalencePlugin {
private Dictionary<ulong, Dictionary<ulong, int>> _proximityticks = new Dictionary<ulong, Dictionary<ulong, int>>();
private HashSet<ulong> _whitelistPlayers = new HashSet<ulong>();
private static string permWhitelist = "teamtracker.whitelist";
private static string permAdmin = "teamtracker.admin";
private static int maxPlayers = 5;
private static int banWeighting = 5;
private static bool banAssociation = true;
private static float proximityDistanceSqrd = 225;
private static string banText = "Banned Reason: Breaking Team Limits";
private static string profileLink = "http://steamcommunity.com/profiles/";
private static TeamTracker? _teamTracker;
private static string onlineEmoji = string.Empty;
private static string offlineEmoji = string.Empty;
private static string teamMemberEmoji = string.Empty;
private static string mainMemberEmoji = string.Empty;
private static string unknownEmoji = string.Empty;
private static string serverName = string.Empty;
private StringBuilder _stringBuilder = new StringBuilder();
#region Init
private void Init() {
_teamTracker = this;
onlineEmoji = $":{config.discordSettings.onlineEmoji}:";
offlineEmoji = $":{config.discordSettings.offlineEmoji}:";
teamMemberEmoji = $":{config.discordSettings.teamMemberEmoji}:";
unknownEmoji = $":{config.discordSettings.unknownEmoji}:";
mainMemberEmoji = $":{config.discordSettings.mainMemberEmoji}:";
maxPlayers = config.MaxPlayers;
banWeighting = config.banSettings.weighting;
banAssociation = config.banSettings.associationBan;
banText = config.banSettings.banText;
proximityDistanceSqrd = (float)Math.Pow(config.proximitySettings.proximityDistance, 2);
profileLink = config.bmSettings.bmProfileLinks ? "https://www.battlemetrics.com/rcon/players/?filter[search]={0}&method=quick&redirect=1" : "http://steamcommunity.com/profiles/{0}";
CheckPlayerVisMask = LayerMask.GetMask("Deployed", "Construction", "World");
_V3up = new Vector3(0, 1.5f, 0);
permission.RegisterPermission(permWhitelist, this);
permission.RegisterPermission(permAdmin, this);
AddCovalenceCommand("clearplayerdata", "ClearPlayerDataCMD");
AddCovalenceCommand("teamcheck", "CheckPlayerDataCMD");
}
Harmony harmony = new Harmony("teamtracker");
private void OnServerInitialized() {
serverName = string.IsNullOrEmpty(config.discordSettings.serverName) ? ConVar.Server.hostname : config.discordSettings.serverName;
if (RelationshipManager.maxTeamSize != maxPlayers) {
Puts($"Warning server may be misconfigured maxteam size is {RelationshipManager.maxTeamSize} and {maxPlayers} usually these should be the same.");
}
foreach (BasePlayer basePlayer in BasePlayer.activePlayerList) {
OnPlayerConnected(basePlayer);
}
if (config.proximitySettings.enabled) {
ServerMgr.Instance.InvokeRepeating(RunProximityChecks, 30f, config.proximitySettings.proximityCheckInterval);
}
ServerMgr.Instance.InvokeRepeating(CheckWebQueue, 30f, 30f);
var classProcessor = harmony.CreateClassProcessor(typeof(BuildingPrivlidge_OnKilled_Patch));
classProcessor.Patch();
var classProcessor2 = harmony.CreateClassProcessor(typeof(BuildingPrivlidge_ServerProjectileHit_Patch));
classProcessor2.Patch();
}
private void CheckWebQueue() {
if (payloadCoroutine != null || payload2Send.Count < 1) return;
payloadCoroutine = SendPayload();
ServerMgr.Instance.StartCoroutine(payloadCoroutine);
}
private void SetupHooks() {
if (!config.turretSettings.enabled) {
Unsubscribe(nameof(OnTurretAuthorize));
}
if (!config.toolcupboardSettings.enabled) {
Unsubscribe(nameof(OnCupboardAuthorize));
}
if (!config.bagSettings.enabled) {
Unsubscribe(nameof(CanAssignBed));
}
if (!config.codeLockSettings.enabled) {
Unsubscribe(nameof(OnCodeEntered));
}
if (!config.healingSettings.enabled) {
Unsubscribe(nameof(OnPlayerRevive));
Unsubscribe(nameof(OnPlayerAssist));
}
if (!config.vehicleSettings.enabled) {
Unsubscribe(nameof(OnEntityMounted));
}
if (!config.raidingSettings.enabled) {
Unsubscribe(nameof(OnExplosiveThrown));
Unsubscribe(nameof(OnRocketLaunched));
}
if (!config.lootingSettings.enabled) {
Unsubscribe(nameof(OnLootEntity));
}
if (!config.pvpSettings.enabled) {
Unsubscribe(nameof(OnPlayerWound));
Unsubscribe(nameof(OnPlayerDeath));
}
}
#endregion Init
#region Configuration
private Configuration config;
public class Configuration {
[JsonProperty("Max Players in a Team")]
public int MaxPlayers = 5;
[JsonProperty("Only count online players")]
public bool _onlinePlayersOnly = false;
[JsonProperty("Clear data on wipe")]
public bool wipeData = true;
[JsonProperty("Discord Settings")]
public DiscordSettings discordSettings = new DiscordSettings();
[JsonProperty("Warning Settings")]
public WarningSettings warningSettings = new WarningSettings();
[JsonProperty("Ban Settings")]
public BanSettings banSettings = new BanSettings();
[JsonProperty("BattleMetrics Settings")]
public BattleMetricsSettings bmSettings = new BattleMetricsSettings();
[JsonProperty("Proximity Settings")]
public ProximitySettings proximitySettings = new ProximitySettings();
[JsonProperty("Team Settings")]
public TeamSettings teamSettings = new TeamSettings();
[JsonProperty("Turret Settings")]
public TurretSettings turretSettings = new TurretSettings();
[JsonProperty("Toolcupboard Settings")]
public ToolcupboardSettings toolcupboardSettings = new ToolcupboardSettings();
[JsonProperty("Bag Settings")]
public BagSettings bagSettings = new BagSettings();
[JsonProperty("CodeLock Settings")]
public CodeLockSettings codeLockSettings = new CodeLockSettings();
[JsonProperty("Healing Settings")]
public HealingSettings healingSettings = new HealingSettings();
[JsonProperty("Vehicle Settings")]
public VehicleSettings vehicleSettings = new VehicleSettings();
[JsonProperty("Raiding Settings")]
public RaidingSettings raidingSettings = new RaidingSettings();
[JsonProperty("PVP Settings")]
public PVPSettings pvpSettings = new PVPSettings();
[JsonProperty("Looting Settings")]
public LootingSettings lootingSettings = new LootingSettings();
[JsonProperty("Lang Settings")]
public LangSettings langSettings = new LangSettings();
public string ToJson() => JsonConvert.SerializeObject(this);
public Dictionary<string, object> ToDictionary() => JsonConvert.DeserializeObject<Dictionary<string, object>>(ToJson());
}
#region Settings
public class LangSettings {
[JsonProperty("Warning Title {0} - Type {1}- Server Name")]
public string warningTitle = "{0} Warning - {1}";
[JsonProperty("Ban Title {0} - Server Name")]
public string banTitle = "Teaming Ban - {0}";
[JsonProperty("Team Request Title {0} - Server Name")]
public string requestTitle = "Team Request - {0}";
[JsonProperty("Player Subtitle")]
public string playerSubtitle = "Player:";
[JsonProperty("UserID Subtitle")]
public string userIDSubtitle = "User ID:";
[JsonProperty("Time Played Subtitle")]
public string timePlayedSubtitle = "Time Played:";
[JsonProperty("Position Subtitle")]
public string posSubtitle = "Pos:";
[JsonProperty("Previous Warnings Subtitle")]
public string previousSubtitle = "Previous Warnings:";
[JsonProperty("Warning Players Subtitle")]
public string warningsSubtitle = "Warning Players:";
[JsonProperty("Warning Players Continued Subtitle")]
public string warningsContSubtitle = "Warning Players Cont:";
[JsonProperty("Current Team Members Subtitle")]
public string currentTeam = "Current Team Members:";
[JsonProperty("Record Team Members Subtitle")]
public string recordedTeam = "Record Team Members:";
[JsonProperty("Associated Players Subtitle")]
public string associatedPlayersSubtitle = "Associated Players";
[JsonProperty("Warning Notes Subtitle")]
public string warningsNotesSubtitle = "Warning Notes:";
[JsonProperty("Ban Notes Subtitle")]
public string banNotesSubtitle = "Notes:";
}
public class DiscordSettings {
[JsonProperty("Server Name")]
public string serverName = string.Empty;
[JsonProperty("Discord Webhook")]
public string discordWebhook = "https://support.discord.com/hc/en-us/articles/228383668-Intro-to-Webhooks";
[JsonProperty("Discord Webhook for Team Checks")]
public string discordWebhookTeamChecks = "https://support.discord.com/hc/en-us/articles/228383668-Intro-to-Webhooks";
[JsonProperty("Team Check Embed Color")]
public string embedColor = "13200406";
[JsonProperty("RoleID to Tag")]
public string roleIDtag = "0";
[JsonProperty("User Name")]
public string userName = "Team Limits Report";
[JsonProperty("Author Icon URL")]
public string authorIcon = "https://files.facepunch.com/garry/f549bfc2-2a49-4eb8-a701-3efd7ae046ac.png";
[JsonProperty("Online Emoji")]
public string onlineEmoji = "green_circle";
[JsonProperty("Offline Emoji")]
public string offlineEmoji = "red_circle";
[JsonProperty("Never Connected Emoji")]
public string unknownEmoji = "black_circle";
[JsonProperty("Main Player Emoji")]
public string mainMemberEmoji = "bust_in_silhouette";
[JsonProperty("Team Member Emoji")]
public string teamMemberEmoji = "busts_in_silhouette";
[JsonProperty("Show Associated Players in warning logs")]
public bool associatedPlayers = true;
[JsonProperty("Show Team Players in warning logs")]
public bool teamPlayers = false;
[JsonProperty("Log Team Checks to Rcon")]
public bool logTeamChecksRcon = false;
}
public class WarningSettings {
[JsonProperty("Length of time to display warning")]
public float warningShowTime = 15f;
[JsonProperty("Use Lang for warning messages")]
public bool useLang = false;
[JsonProperty("Warning Text")]
public string warningText = "<size=11><color=black><b>This server has a team limit of {0} players<b></color> <br>to swap team members contact support";
}
public class BanSettings {
[JsonProperty("Enable Bans")]
public bool enabled = false;
[JsonProperty("Log Bans to Discord")]
public bool logBans = true;
[JsonProperty("Discord Webhook for Bans")]
public string discordWebhook = "https://support.discord.com/hc/en-us/articles/228383668-Intro-to-Webhooks";
[JsonProperty("Ban Duration (days)")]
public int banDuration = 7;
[JsonProperty("Ban Text")]
public string banText = "Banned Reason: Breaking Team Limits";
[JsonProperty("Local Bans")]
public bool localBan = false;
[JsonProperty("Ban Whole Team")]
public bool banTeam = false;
[JsonProperty("Ban by Max Warnings")]
public bool maxWarningsBan = true;
[JsonProperty("Max Warnings")]
public int maxWarnings = 3;
[JsonProperty("Ban by Unique Warnings")]
public bool uniqueWarningsBan = true;
[JsonProperty("Max Unique Warnings")]
public int uniqueWarnings = 3;
[JsonProperty("Enable Ban by Max Association")]
public bool associationBan = true;
[JsonProperty("Max Association Weighting")]
public int weighting = 5;
[JsonProperty("Min Association for Logging")]
public int weightingMin = 2;
[JsonProperty("Ban all associated players above max weighting")]
public bool banAssociatedPlayers = false;
[JsonProperty("Embed Color")]
public string embedColor = "0";
}
public class BattleMetricsSettings {
[JsonProperty("Use BM profile links")]
public bool bmProfileLinks = false;
[JsonProperty("BattleMetrics Bans")]
public bool battlemetricsBan = false;
[JsonProperty("BattleMetrics API")]
public string APIKey = string.Empty;
[JsonProperty("BattleMetrics serverID")]
public string serverID = string.Empty;
[JsonProperty("BattleMetrics orgID")]
public string orgID = string.Empty;
[JsonProperty("BattleMetrics BanList ID")]
public string banListID = string.Empty;
[JsonProperty("BattleMetrics Org Wide Ban")]
public bool orgWide = false;
[JsonProperty("BattleMetrics Auto Add")]
public bool autoAdd = false;
[JsonProperty("BattleMetrics Native Enabled")]
public bool native = false;
}
public class ProximitySettings {
[JsonProperty("Enable Proximity Checks")]
public bool enabled = true;
[JsonProperty("Log to Discord")]
public bool logtoDiscord = true;
[JsonProperty("Proximity Distance")]
public float proximityDistance = 15f;
[JsonProperty("Proximity Check Interval (seconds)")]
public float proximityCheckInterval = 360f;
[JsonProperty("Ticks per Report")]
public int proximityAlertTicks = 2;
[JsonProperty("Online Time Reduction Factor (Reduce ticks by % of online time)")]
public float onlineTimeFactor = 10;
[JsonProperty("Proximity Vision Checks")]
public bool proximityVision = true;
[JsonProperty("Warn Players")]
public bool showWarning = false;
[JsonProperty("Weighting")]
public float weighting = 1f;
[JsonProperty("Embed Color")]
public string embedColor = "13200406";
}
public class TurretSettings {
[JsonProperty("Enable Turret Checks")]
public bool enabled = true;
[JsonProperty("Log to Discord")]
public bool logtoDiscord = true;
[JsonProperty("Warn Players")]
public bool showWarning = false;
[JsonProperty("Weighting")]
public float weighting = 1f;
[JsonProperty("Embed Color")]
public string embedColor = "16711680";
}
public class TeamSettings {
[JsonProperty("Enable Team Member Checks")]
public bool enabled = true;
[JsonProperty("Log to Discord")]
public bool logtoDiscord = true;
[JsonProperty("Warn Players")]
public bool showWarning = false;
[JsonProperty("Weighting")]
public float weighting = 1f;
[JsonProperty("Embed Color")]
public string embedColor = "4299569";
}
public class ToolcupboardSettings {
[JsonProperty("Enable Toolcupboard Checks")]
public bool enabled = false;
[JsonProperty("Log to Discord")]
public bool logtoDiscord = true;
[JsonProperty("Warn Players")]
public bool showWarning = false;
[JsonProperty("Weighting")]
public float weighting = 0.2f;
[JsonProperty("Embed Color")]
public string embedColor = "11952688";
}
public class BagSettings {
[JsonProperty("Enable Bag Checks")]
public bool enabled = true;
[JsonProperty("Log to Discord")]
public bool logtoDiscord = true;
[JsonProperty("Warn Players")]
public bool showWarning = false;
[JsonProperty("Weighting")]
public float weighting = 1f;
[JsonProperty("Embed Color")]
public string embedColor = "2042328";
}
public class CodeLockSettings {
[JsonProperty("Enable CodeLock Checks")]
public bool enabled = true;
[JsonProperty("Log to Discord")]
public bool logtoDiscord = true;
[JsonProperty("Warn Players")]
public bool showWarning = false;
[JsonProperty("Weighting")]
public float weighting = 1f;
[JsonProperty("Embed Color")]
public string embedColor = "13200406";
}
public class HealingSettings {
[JsonProperty("Enable Healing Checks")]
public bool enabled = true;
[JsonProperty("Warn Players")]
public bool showWarning = false;
[JsonProperty("Weighting")]
public float weighting = 0.5f;
[JsonProperty("Team Association weighting limit for logging")]
public float weightingFilter = 2f;
[JsonProperty("Log to Discord")]
public bool logtoDiscord = false;
[JsonProperty("Embed Color")]
public string embedColor = "9961352";
}
public class VehicleSettings {
[JsonProperty("Enable Vehicle Checks")]
public bool enabled = true;
[JsonProperty("Warn Players")]
public bool showWarning = false;
[JsonProperty("Weighting")]
public float weighting = 0.5f;
[JsonProperty("Team Association weighting limit for logging")]
public float weightingFilter = 2f;
[JsonProperty("Log to Discord")]
public bool logtoDiscord = false;
[JsonProperty("Embed Color")]
public string embedColor = "12263372";
}
public class RaidingSettings {
[JsonProperty("Enable Raiding Checks")]
public bool enabled = true;
[JsonProperty("Warn Players")]
public bool showWarning = false;
[JsonProperty("Weighting")]
public float weighting = 0.5f;
[JsonProperty("Team Association weighting limit for logging")]
public float weightingFilter = 2f;
[JsonProperty("Log to Discord")]
public bool logtoDiscord = false;
[JsonProperty("Embed Color")]
public string embedColor = "12263372";
}
public class PVPSettings {
[JsonProperty("Enable PVP Checks")]
public bool enabled = true;
[JsonProperty("Warn Players")]
public bool showWarning = false;
[JsonProperty("Weighting")]
public float weighting = 0.5f;
[JsonProperty("Team Association weighting limit for logging")]
public float weightingFilter = 2f;
[JsonProperty("Log to Discord")]
public bool logtoDiscord = false;
[JsonProperty("Embed Color")]
public string embedColor = "12263372";
}
public class LootingSettings {
[JsonProperty("Enable Looting Checks")]
public bool enabled = true;
[JsonProperty("Warn Players")]
public bool showWarning = false;
[JsonProperty("Weighting")]
public float weighting = 0.5f;
[JsonProperty("Team Association weighting limit for logging")]
public float weightingFilter = 2f;
[JsonProperty("Log to Discord")]
public bool logtoDiscord = false;
[JsonProperty("Embed Color")]
public string embedColor = "12263372";
}
#endregion Settings
protected override void LoadDefaultConfig() => config = new Configuration();
protected override void LoadConfig() {
base.LoadConfig();
try {
config = Config.ReadObject<Configuration>();
if (config == null) {
throw new JsonException();
}
SaveConfig();
}
catch {
Puts($"Configuration file {Name}.json is invalid; using defaults");
LoadDefaultConfig();
}
}
protected override void SaveConfig() {
LogWarning($"Configuration changes saved to {Name}.json");
Config.WriteObject(config, true);
}
private void Loaded() {
SetupHooks();
_ProximityDataCFG = Interface.Oxide.DataFileSystem.GetFile("TeamTracker/ProximityData");
_BagsDataCFG = Interface.Oxide.DataFileSystem.GetFile("TeamTracker/BagsData");
_PlayerDataCFG = Interface.Oxide.DataFileSystem.GetFile("TeamTracker/PlayerData");
_PlayerTimeDataCFG = Interface.Oxide.DataFileSystem.GetFile("TeamTracker/PlayerTimeData");
_PlayerInfoCFG = Interface.Oxide.DataFileSystem.GetFile("TeamTracker/PlayerInfo");
LoadData();
}
#endregion Configuration
#region Localization
protected override void LoadDefaultMessages() {
lang.RegisterMessages(new Dictionary<string, string> {
["WarningText"]
= "<size=11><color=black><b>This server has a team limit of {0} players<b></color> <br>to swap team members contact support"
}
, this);
}
#endregion Localization
#region Data
private Dictionary<ulong, PlayerData> _playerData = new Dictionary<ulong, PlayerData>();
public void AssociationCheck() {
foreach (var playerData in _AssociationCheckQueue) {
int i = 0;
foreach (var player in playerData._associatedPlayers) {
if (player.Value < banWeighting) continue;
if (config._onlinePlayersOnly && _playerInfo.TryGetValue(player.Key, out var playerInfo) && !playerInfo.online) {
continue;
}
i++;
}
if (i < maxPlayers) return;
if (banAssociation) {
PlayerInfo? mainPlayerinfo = GetPlayerInfo(playerData.PlayerID);
string warnings = string.Empty;
foreach (var warning in playerData._warnings) {
warnings += $"{warning},";
}
StringBuilder stringBuilder = _stringBuilder;
stringBuilder.Clear();
stringBuilder.Append($"Ban by Association Limits\n**Player:**{mainPlayerinfo?.name}-{playerData.PlayerID}\n**Previous Warnings:**{warnings}\n**Associated Players:**\n");
List<KeyValuePair<ulong, float>> associatedPlayersList = Pool.Get<List<KeyValuePair<ulong, float>>>();
associatedPlayersList.AddRange(playerData._associatedPlayers.OrderByDescending(kv => kv.Value));
foreach (var player in associatedPlayersList) {
PlayerInfo? playerinfo = GetPlayerInfo(player.Key);
if (playerinfo == null) continue;
if (player.Value < config.banSettings.weightingMin) {
break;
}
string pnotes = $"{playerinfo.name} - {player.Key} **[{player.Value}]** \n";
if (stringBuilder.Length + pnotes.Length > 1024) {
break;
}
stringBuilder.Append(pnotes);
}
Pool.FreeUnmanaged(ref associatedPlayersList);
BanPlayer(playerData.PlayerID, banText, stringBuilder.ToString());
}
}
_AssociationCheckQueue.Clear();
}
public class PlayerData {
public int WarningCount = 0;
public ulong PlayerID;
public bool Banned = false;
public Dictionary<WarningType, int> _warnings = new Dictionary<WarningType, int>();
public Dictionary<ulong, float> _associatedPlayers = new Dictionary<ulong, float>();
public HashSet<ulong> _teamHistory = new HashSet<ulong>();
}
private PlayerData? AddAssociationData(ulong playerID, ulong associatedPlayerID, float weighting) {
if (playerID == 0 || playerID == associatedPlayerID) return null;
if (!_playerData.TryGetValue(playerID, out var playerdata)) {
playerdata = new PlayerData() {
PlayerID = playerID
}
;
_playerData.Add(playerID, playerdata);
}
AddAssociation(playerdata, associatedPlayerID, weighting);
return playerdata;
}
private PlayerData? AddAssociationDataAll(ulong playerID, HashSet<ulong> playerid, float weighting) {
if (playerID == 0) return null;
if (!_playerData.TryGetValue(playerID, out var playerdata)) {
playerdata = new PlayerData() {
PlayerID = playerID
}
;
_playerData.Add(playerID, playerdata);
}
foreach (var player in playerid) {
AddAssociationData(playerID, playerid, weighting);
}
return playerdata;
}
private PlayerData? AddAssociationData(ulong playerID, HashSet<ulong> playerid, float weighting) {
if (playerID == 0) return null;
if (!_playerData.TryGetValue(playerID, out var playerdata)) {
playerdata = new PlayerData() {
PlayerID = playerID
}
;
_playerData.Add(playerID, playerdata);
}
foreach (var player in playerid) {
AddAssociation(playerdata, player, weighting);
}
return playerdata;
}
private HashSet<PlayerData> _AssociationCheckQueue = new HashSet<PlayerData>();
private void AddAssociation(PlayerData playerdata, ulong playerid, float weighting) {
if (playerid == playerdata.PlayerID || playerid == 0) {
return;
}
if (playerdata._associatedPlayers.TryGetValue(playerid, out float count)) {
playerdata._associatedPlayers[playerid] = count + (float)Math.Round(weighting, 3);
}
else {
playerdata._associatedPlayers.Add(playerid, weighting);
}
_AssociationCheckQueue.Add(playerdata);
}
public void AddAssociation(PlayerData playerdata, HashSet<ulong> playerid, float weighting) {
foreach (var player in playerid) {
AddAssociation(playerdata, player, weighting);
}
}
private DynamicConfigFile _TeamDataCFG;
private DynamicConfigFile _ProximityDataCFG;
private DynamicConfigFile _BagsDataCFG;
private DynamicConfigFile _PlayerDataCFG;
private DynamicConfigFile _PlayerInfoCFG;
private DynamicConfigFile _PlayerTimeDataCFG;
private void LoadData() {
try {
_proximityticks = _ProximityDataCFG.ReadObject<Dictionary<ulong, Dictionary<ulong, int>>>();
}
catch {
_proximityticks = new Dictionary<ulong, Dictionary<ulong, int>>();
}
try {
_BagsList = _BagsDataCFG.ReadObject<Dictionary<uint, List<BagInfo>>>();
}
catch {
_BagsList = new Dictionary<uint, List<BagInfo>>();
}
try {
_playerData = _PlayerDataCFG.ReadObject<Dictionary<ulong, PlayerData>>();
}
catch {
_playerData = new Dictionary<ulong, PlayerData>();
}
try {
_playerTimeData = _PlayerTimeDataCFG.ReadObject<Dictionary<ulong, float>>();
}
catch {
_playerTimeData = new Dictionary<ulong, float>();
}
try {
_playerInfo = _PlayerInfoCFG.ReadObject<Dictionary<ulong, PlayerInfo>>();
}
catch {
_playerInfo = new Dictionary<ulong, PlayerInfo>();
}
Puts("Data Loaded");
}
private void Unload() {
ServerMgr.Instance.CancelInvoke(RunProximityChecks);
foreach (var player in BasePlayer.activePlayerList) {
OnPlayerDisconnected(player, string.Empty);
}
SaveData();
harmony.UnpatchAll("teamtracker");
_teamTracker = null;
}
private void SaveData() {
_BagsDataCFG.WriteObject(_BagsList);
_ProximityDataCFG.WriteObject(_proximityticks);
_PlayerDataCFG.WriteObject(_playerData);
_PlayerTimeDataCFG.WriteObject(_playerTimeData);
_PlayerInfoCFG.WriteObject(_playerInfo);
}
void OnNewSave(string filename) {
if (!config.wipeData) return;
_proximityticks.Clear();
_BagsList.Clear();
_playerData.Clear();
_playerTimeData.Clear();
_playerInfo.Clear();
SaveData();
}
#endregion Data
#region Commands
private void ClearPlayerDataCMD(IPlayer iplayer, string command, string[] args) {
if (!HasPerm(iplayer.Id, permAdmin)) return;
if (args.Length == 0) {
iplayer.Message("Syntax: /clearplayerdata <playername or steamid>");
return;
}
IPlayer? target = covalence.Players.FindPlayer(args[0]);
if (target == null) {
iplayer.Message("Player not found");
return;
}
ulong targetID = ulong.Parse(target.Id);
_playerData.Remove(targetID);
_proximityticks.Remove(targetID);
_playerTimeData.Remove(targetID);
foreach (var player in _playerData) {
PlayerData pdata = player.Value;
pdata._associatedPlayers.Remove(targetID);
pdata._teamHistory.Remove(targetID);
}
foreach (var player in _proximityticks) {
player.Value.Remove(targetID);
}
foreach (var building in _BagsList) {
foreach (var bag in building.Value) {
if (bag.OwnerID == targetID) {
building.Value.Remove(bag);
continue;
}
if (bag.deployerUserID == targetID) {
building.Value.Remove(bag);
continue;
}
}
}
SaveData();
iplayer.Message($"Player Data Cleared for {target.Name}");
}
private void CheckPlayerDataCMD(IPlayer iplayer, string command, string[] args) {
if (!HasPerm(iplayer.Id, permAdmin)) return;
if (args.Length == 0) {
iplayer.Message("Syntax: /teamcheck <playername or steamid>");
return;
}
IPlayer? target = covalence.Players.FindPlayer(args[0]);
if (target == null) {
iplayer.Message("Player not found");
return;
}
ulong targetID = ulong.Parse(target.Id);
if (!_playerData.TryGetValue(targetID, out var pdata)) {
iplayer.Message($"Player {target.Name} has no associations");
return;
}
if (!iplayer.IsServer) iplayer.Message($"Player {target.Name} data sent to discord");
if (iplayer.IsServer && config.discordSettings.logTeamChecksRcon) {
SendRequestRCON(targetID, pdata);
}
SendRequestDiscord(targetID, pdata);
}
#endregion Commands
#region PlayerTimeTracker
private Dictionary<ulong, float> _playerTimeData = new Dictionary<ulong, float>();
private Dictionary<ulong, float> _timeconnected = new Dictionary<ulong, float>();
private Dictionary<ulong, PlayerInfo> _playerInfo = new Dictionary<ulong, PlayerInfo>();
private class PlayerInfo {
public ulong steamid;
public string name;
public bool online;
}
void OnPlayerConnected(BasePlayer player) {
if (permission.UserHasPermission(player.UserIDString, permWhitelist) || player.IsAdmin || player.IsDeveloper) {
_whitelistPlayers.Add(player.userID);
return;
}
_timeconnected[player.userID] = Time.time;
if (!_playerData.TryGetValue(player.userID, out var pdata)) {
pdata = new PlayerData() {
PlayerID = player.userID
}
;
_playerData.Add(player.userID, pdata);
}
if (!_playerInfo.TryGetValue(player.userID, out var playerInfo)) {
playerInfo = new PlayerInfo() {
steamid = player.userID, name = player.displayName, online = true
}
;
_playerInfo.Add(player.userID, playerInfo);
}
else {
playerInfo.name = player.displayName;
playerInfo.online = true;
}
if (player.Team != null) {
foreach (var member in player.Team.members) {
pdata._teamHistory.Add(member);
}
}
}
void OnPlayerDisconnected(BasePlayer player, string reason) {
if (_playerInfo.TryGetValue(player.userID, out var playerInfo)) {
playerInfo.online = false;
}
if (_timeconnected.TryGetValue(player.userID, out float connectionTime)) {
float totalTime = Time.time - connectionTime;
if (_playerTimeData.ContainsKey(player.userID)) {
_playerTimeData[player.userID] += totalTime;
}
else {
_playerTimeData[player.userID] = totalTime;
}
_timeconnected.Remove(player.userID);
}
}
#endregion PlayerTimeTracker
#region Checks
#region Codelocks
private void OnCodeEntered(CodeLock codeLock, BasePlayer player, string code) {
if (code != codeLock.code && code != codeLock.guestCode) return;
if (IsWhitelisted(player.userID)) {
return;
}
HashSet<ulong> authedplayers = Pool.Get<HashSet<ulong>>();
foreach (var entry in codeLock.whitelistPlayers) {
if (IsWhitelisted(entry)) continue;
authedplayers.Add(entry);
}
foreach (var entry in codeLock.guestPlayers) {
if (IsWhitelisted(entry)) continue;
authedplayers.Add(entry);
}
authedplayers.Remove(player.userID);
//update other players
foreach (var authedplayer in authedplayers) {
if (!_playerData.TryGetValue(authedplayer, out var data)) {
data = new PlayerData() {
PlayerID = authedplayer
}
;
_playerData.Add(authedplayer, data);
}
AddAssociation(data, player.userID, config.codeLockSettings.weighting);
}
//update new auth
if (!_playerData.TryGetValue(player.userID, out var pdata)) {
pdata = new PlayerData() {
PlayerID = player.userID
}
;
_playerData.Add(player.userID, pdata);
}
AddAssociation(pdata, authedplayers, config.codeLockSettings.weighting);
if (authedplayers.Count < maxPlayers) {
Pool.FreeUnmanaged(ref authedplayers);
return;
}
//Send Alert
WarnPlayers(authedplayers, WarningType.CodeLock, config.codeLockSettings.showWarning);
WarnPlayer(player.userID, WarningType.CodeLock, config.codeLockSettings.showWarning);
//Send to Discord
if (config.codeLockSettings.logtoDiscord) {
SendToDiscord(player.userID, pdata, WarningType.CodeLock, authedplayers, config.codeLockSettings.embedColor);
}
Pool.FreeUnmanaged(ref authedplayers);
}
#endregion Codelocks
#region Bags
//remove bags data for raided base
[HarmonyPatch(typeof(BuildingPrivlidge), "OnDied")]
// Change this to Die
public static class BuildingPrivlidge_OnKilled_Patch {
[HarmonyPrefix]
public static void Prefix(BuildingPrivlidge __instance, HitInfo info) {
if (info == null || __instance.buildingID == 0) {
return;
}
if (info.Initiator is BasePlayer player) {
if (__instance.IsAuthed(player)) return;
}
_teamTracker._BagsList.Remove(__instance.buildingID);
}
}
private Dictionary<uint, List<BagInfo>> _BagsList = new Dictionary<uint, List<BagInfo>>();
private struct BagInfo {
public ulong deployerUserID;
public ulong OwnerID;
}
//lets get a hook for when a bag is assigned
private void CanAssignBed(BasePlayer player, SleepingBag bag, ulong targetPlayerId) {
if (bag.buildingID != 0) {
NextTick(() => {
BedCheck(bag);
}
);
return;
}
if (player.userID == targetPlayerId) return;
if (TeamMembersCount(player) >= maxPlayers && !HasTeamMember(player, targetPlayerId)) {
//Send Alert
WarnPlayer(targetPlayerId, WarningType.Bag, config.bagSettings.showWarning);
WarnPlayer(player.userID, WarningType.Bag, config.bagSettings.showWarning);
if (config.bagSettings.logtoDiscord) {
//Send to Discord
SendToDiscord(bag.OwnerID, _playerData[bag.OwnerID], WarningType.Bag, new HashSet<ulong> {
targetPlayerId
}
, config.bagSettings.embedColor, $"Sleepingbag given to {targetPlayerId} by {player.userID}");
}
}
//add association for placer
AddAssociationData(player.userID, targetPlayerId, config.bagSettings.weighting);
//add association for recipient
AddAssociationData(targetPlayerId, player.userID, config.bagSettings.weighting);
}
[HarmonyPatch(typeof(SleepingBag), "OnPlaced"), AutoPatch]
public static class OnPlaced_Patch {
[HarmonyPrefix]
public static void Postfix(SleepingBag __instance, BasePlayer player) {
if (__instance == null || __instance.buildingID == 0) {
return;
}
__instance.OwnerID = player.userID;
__instance.deployerUserID = player.userID;
_teamTracker.BedCheck(__instance);
}
}
private void BedCheck(SleepingBag bag) {
if (!config.bagSettings.enabled) return;
BagInfo bagInfo = new BagInfo {
deployerUserID = bag.deployerUserID, OwnerID = bag.OwnerID,
}
;
if (!_BagsList.TryGetValue(bag.buildingID, out List<BagInfo>? baglist)) {
baglist = new List<BagInfo>() {
bagInfo
}
;
_BagsList.Add(bag.buildingID, baglist);
return;
}
baglist.Add(bagInfo);
HashSet<ulong> authedplayers = Pool.Get<HashSet<ulong>>();
foreach (BagInfo pbag in baglist) {
if (IsWhitelisted(pbag.OwnerID) || IsWhitelisted(pbag.deployerUserID)) continue;
authedplayers.Add(pbag.OwnerID);
authedplayers.Add(pbag.deployerUserID);
}
foreach (var authedplayer in authedplayers) {
AddAssociationData(authedplayer, authedplayers, config.bagSettings.weighting);
}
if (authedplayers.Count <= maxPlayers) {
Pool.FreeUnmanaged(ref authedplayers);
return;
}
//Send Warnings
WarnPlayers(authedplayers, WarningType.Bag, config.bagSettings.showWarning);
authedplayers.Remove(bag.OwnerID);
if (config.bagSettings.logtoDiscord) {
//Send to Discord
SendToDiscord(bag.OwnerID, _playerData[bag.OwnerID], WarningType.Bag, authedplayers, config.bagSettings.embedColor, $"Sleepingbag placed for {bag.deployerUserID} deployed by {bag.OwnerID}");
}
Pool.FreeUnmanaged(ref authedplayers);
}
#endregion
#region Turrets
private void OnTurretAuthorize(AutoTurret turret, BasePlayer player) {
if (IsWhitelisted(player.userID)) return;
BuildingPrivlidge buildingPrivlidge = turret.GetBuildingPrivilege();
if (buildingPrivlidge == null || buildingPrivlidge.cachedProtectedMinutes < 1) return;
int count = 1;
HashSet<ulong> authedplayers = Pool.Get<HashSet<ulong>>();
foreach (var entry in turret.authorizedPlayers) {
if (IsWhitelisted(entry.userid)) continue;
if (entry.userid == player.userID) {
count = 0;
continue;
}
authedplayers.Add(entry.userid);
}
foreach (var authedplayer in authedplayers) {
AddAssociationData(authedplayer, player.userID, config.turretSettings.weighting);
}
PlayerData? pdata = AddAssociationData(player.userID, authedplayers, config.turretSettings.weighting);
if (pdata == null || turret.authorizedPlayers.Count + count <= maxPlayers) {
Pool.FreeUnmanaged(ref authedplayers);
return;
}
WarnPlayers(authedplayers, WarningType.Turret, config.turretSettings.showWarning);
WarnPlayer(player.userID, WarningType.Turret, config.turretSettings.showWarning);
if (config.turretSettings.logtoDiscord) {
SendToDiscord(player.userID, pdata, WarningType.Turret, authedplayers, config.turretSettings.embedColor);
}
}
#endregion Turrets
#region Toolcupboard
private void OnCupboardAuthorize(BuildingPrivlidge privilege, BasePlayer player) {
if (IsWhitelisted(player.userID)) {
return;
}
if (privilege.cachedProtectedMinutes == 0 && Time.time - privilege.timePlaced > 900) return;
HashSet<ulong> authedplayers = Pool.Get<HashSet<ulong>>();
int count = 1;
foreach (var entry in privilege.authorizedPlayers) {
if (IsWhitelisted(entry.userid)) continue;
if (entry.userid == player.userID) {
count = 0;
continue;
}
authedplayers.Add(entry.userid);
}
foreach (var authedplayer in authedplayers) {
AddAssociationData(authedplayer, player.u
// ... [Code preview truncated due to length. Full source in attached file] ...