C#:
using Rust;
using System;
using System.Text;
using System.Linq;
using System.Reflection;
using System.Collections.Generic;
using UnityEngine;
using Oxide.Core;
using Oxide.Core.Plugins;
using Oxide.Game.Rust.Cui;
using Oxide.Core.Database;
using Oxide.Core.Libraries;
using Oxide.Core.Configuration;
using Oxide.Core.Libraries.Covalence;
using Rust.Ai.Gen2;
//Suggestion MikeTheVike - Add "use /command to see full leaderboard" after the chat top X
//To do
// Ping leaders or title holders to discord.
// Auto-wipe PTT
// Prioritise titles
// Add TeamID for SQL use
// Add API for David
// Changed sql columns to allow for 4 byte unicode characters
// Look into firebase save
// Add PVPDeaths and use it (optionally) to calculate KDR
// Add TeamID
// TO DO
// New data file with commands to run for top 1/2/3 per category.
// Auto run these commands OnNewSave, prior to wipe.
// Allow option to run these commands on user command, either by category or for all at once.
// Added TakeLBSnapShotAtWipe for auto snapshot taking
// Added support for new wolves.
// Remove Remote/RF C4 (others?) from PVE Distance.
// Perma store SaveRestore.WipeId and compare, instead of using OnNewSave();
// OnCollectiblePickup NRE fix.
// Shark and snakes now contribute to PVE_Kills.
// All other new animals should already contribute.
// Made PlaytimeTracker update on player disconnect, as well as existing connect check.
// Added update titles when individual player info is updated - Should address issues with PlaytimeTracker title order.
// V2.3.4
// Fix for July wipe.
namespace Oxide.Plugins {
[Info("PlayerRanks", "Steenamaroo", "2.3.4", ResourceId = 14)]
class PlayerRanks : RustPlugin {
//void APITest()
//{
// var result = PlayerRanks.CallHook("GetTop", "PVPKills", 10, new List<ulong>() { 76561198025512345 }) as Dictionary<ulong, Dictionary<string, object>>;
// foreach (var entry in result)
// foreach (var stat in result[entry.Key])
// Puts(stat.Key + " " + stat.Value.ToString());
// var playerscore = PlayerRanks.CallHook("GetPlayerScore", (ulong)76561198025512345, "PVPKills");
// Puts(playerscore.ToString());
//}
#region Declarations
[PluginReference]
Plugin Clans, Friends, EventManager, PlaytimeTracker, Economics, ServerRewards, Arena;
Core.MySql.Libraries.MySql Sql = Interface.Oxide.GetLibrary<Core.MySql.Libraries.MySql>();
Connection Sql_conn;
bool loaded = false;
List<string> intenseOptions = new List<string>() {
"StructuresBuilt", "StructuresDemolished", "ItemsDeployed", "ItemsCrafted", "EntitiesRepaired", "ResourcesGathered", "StructuresUpgraded"
}
;
Dictionary<ulong, Dictionary<ulong, float>> BradleyAttackers = new Dictionary<ulong, Dictionary<ulong, float>>();
Dictionary<ulong, Dictionary<ulong, int>> HeliAttackers = new Dictionary<ulong, Dictionary<ulong, int>>();
Dictionary<ulong, WoundedData> woundedData = new Dictionary<ulong, WoundedData>();
Dictionary<ulong, string> MenuOpen = new Dictionary<ulong, string>();
List<ulong> airdrops = new List<ulong>();
const string font = "robotocondensed-regular.ttf";
bool HasPermission(string id, string perm) => permission.UserHasPermission(id, perm);
const string permUse = "playerranks.use";
const string permAdmin = "playerranks.admin";
const string permExcludeFromStats = "playerranks.excludefromstats";
const string permExcludedFromStats = "playerranks.excludedfromstats";
const string permOptOut = "playerranks.optout";
List<string> Broadcast = new List<string>();
Timer BroadcastTimer;
#endregion
#region RustIO
Library lib;
MethodInfo isInstalled;
MethodInfo hasFriend;
bool IsInstalled() => lib != null && isInstalled != null && (bool)isInstalled.Invoke(lib, new object[] {
}
);
bool HasFriend(string playerId, string friendId) => lib != null && hasFriend != null && (bool)hasFriend.Invoke(lib, new object[] {
playerId, friendId
}
);
#endregion
#region Titles
private void OnPluginLoaded(Plugin plugin) {
if (loaded && conf.Titles.EnablePlayerTitles && plugin.Title == "Better Chat") Interface.CallHook("API_RegisterThirdPartyTitle", this, new Func<IPlayer, string>(GetBCTitle));
}
public class Top {
public ulong id = 0;
public double score = 0;
}
public Dictionary<string, Top> TitleList = new Dictionary<string, Top>() {
{
"PVPKills", new Top()
}
, {
"PVPDistance", new Top()
}
, {
"PVEKills", new Top()
}
, {
"PVEDistance", new Top()
}
, {
"NPCKills", new Top()
}
, {
"NPCDistance", new Top()
}
, {
"SleepersKilled", new Top()
}
, {
"HeadShots", new Top()
}
, {
"Deaths", new Top()
}
, {
"Suicides", new Top()
}
, {
"KDR", new Top()
}
, {
"SDR", new Top()
}
, {
"SkullsCrushed", new Top()
}
, {
"TimesWounded", new Top()
}
, {
"TimesHealed", new Top()
}
, {
"HeliHits", new Top()
}
, {
"HeliKills", new Top()
}
, {
"APCHits", new Top()
}
, {
"APCKills", new Top()
}
, {
"BarrelsDestroyed", new Top()
}
, {
"ExplosivesThrown", new Top()
}
, {
"ArrowsFired", new Top()
}
, {
"BulletsFired", new Top()
}
, {
"RocketsLaunched", new Top()
}
, {
"MLRSFired", new Top()
}
, {
"WeaponTrapsDestroyed", new Top()
}
, {
"DropsLooted", new Top()
}
, {
"FishCaught", new Top()
}
, {
"Economics", new Top()
}
, {
"ServerRewards", new Top()
}
, {
"StructuresBuilt", new Top()
}
, {
"StructuresDemolished", new Top()
}
, {
"ItemsDeployed", new Top()
}
, {
"ItemsCrafted", new Top()
}
, {
"EntitiesRepaired", new Top()
}
, {
"ResourcesGathered", new Top()
}
, {
"PlantsGathered", new Top()
}
, {
"StructuresUpgraded", new Top()
}
, {
"TimePlayed", new Top()
}
}
;
public Dictionary<string, Timer> CoolDowns = new Dictionary<string, Timer>();
void DoTitle(ulong id, string category) {
if (!conf.Titles.EnablePlayerTitles || IntenseBlock(category)) return;
if (HasPermission(id.ToString(), permExcludedFromStats) || (bool)data.PlayerRankData[id]["OptOut"] == true || (conf.Options.allowadmin == false && (bool)data.PlayerRankData[id]["Admin"] == true)) return;
var path = data.PlayerRankData[id];
if (D(path[category]) > TitleList[category].score && TitleList[category].id != id) {
if (conf.CategorySettings[category].BroadcastTitleChanges) {
BasePlayer taker = BasePlayer.FindByID(id);
if (taker != null) {
if (!CoolDowns.ContainsKey(category)) CoolDowns.Add(category, timer.Once(1f, () => {
}
));
if (conf.CategorySettings[category].Title != string.Empty) {
ulong loser = TitleList[category].id;
CoolDowns[category] = timer.Once(5f, () => {
foreach (var player in BasePlayer.activePlayerList.Where(player => (bool)data.PlayerRankData[player.userID]["PrintToChat"] == true)) if (conf.Options.GlobalTitleBroadcast || (taker == player || player.userID == loser)) SendReply(player, lang.GetMessage(player.userID == loser ? "losttitle" : "tooktitle", this), taker.displayName, conf.CategorySettings[category].Title);
}
);
}
}
}
if (conf.Titles.AddTitleHoldersToGroup) {
ClearGroup(category);
permission.AddUserGroup(id.ToString(), category);
}
TitleList[category] = new Top() {
id = id, score = D(path[category])
}
;
}
}
private string GetBCTitle(IPlayer player) => GetTitle(player, true);
private string GetPlayerTitle(IPlayer player) => GetTitle(player, false);
private string GetTitle(IPlayer player, bool BetterChat) {
string title = conf.Titles.TitleStart;
int added = 0;
foreach (var entry in TitleList) {
if (BetterChat && !conf.CategorySettings[entry.Key].ShowTitleInPlayerChatMessages) continue;
if (added < conf.Titles.MaxDisplayedTitles && entry.Value.id == Convert.ToUInt64(player.Id) && conf.CategorySettings[entry.Key].Title != string.Empty) {
if (added > 0) title += " ";
title += $"{conf.CategorySettings[entry.Key].Title}";
added++;
}
}
title += conf.Titles.TitleEnd;
if (added > conf.Titles.MaxTitlesBeforeLineBreak) title += "\n";
return added == 0 ? "" : title;
}
void LoadTitles() {
if (!loaded) return;
if (!conf.Titles.EnablePlayerTitles || conf.Titles.AddTitleHoldersToGroup) foreach (var cat in conf.CategorySettings) permission.RemoveGroup(cat.Key);
if (!conf.Titles.EnablePlayerTitles) return;
var dictToUse = data.PlayerRankData.Where(pair => !HasPermission(pair.Key.ToString(), permExcludedFromStats) && (bool)pair.Value["OptOut"] == false && (conf.Options.allowadmin == true || (bool)pair.Value["Admin"] == false)).ToDictionary(val => val.Key, val => val.Value);
foreach (var cat in conf.CategorySettings.Where(cat => conf.CategorySettings[cat.Key].Title != string.Empty && !IntenseBlock(cat.Key))) {
if (NoPlug(cat.Key) || dictToUse.Count == 0) continue;
var top = dictToUse.Aggregate((l, r) => D(l.Value[cat.Key]) > D(r.Value[cat.Key]) ? l : r);
if (D(top.Value[cat.Key]) > 0) {
if (conf.Titles.AddTitleHoldersToGroup) {
ClearGroup(cat.Key);
permission.AddUserGroup(top.Key.ToString(), cat.Key);
}
TitleList[cat.Key] = new Top() {
id = top.Key, score = D(top.Value[cat.Key])
}
;
}
else if(conf.Titles.AddTitleHoldersToGroup) ClearGroup(cat.Key);
}
Interface.CallHook("API_RegisterThirdPartyTitle", this, new Func<IPlayer, string>(GetBCTitle));
}
void ClearGroup(string cat) {
if (permission.GroupExists(cat)) {
foreach (string member in permission.GetUsersInGroup(cat).ToList()) permission.RemoveUserGroup(member, cat);
}
else permission.CreateGroup(cat, cat, 0);
}
#endregion
#region DataStorage
public DelayRewards dr;
public class DelayRewards {
public Dictionary<ulong, List<DelayedRewards>> DR = new Dictionary<ulong, List<DelayedRewards>>();
}
public class DelayedRewards {
public string category;
public string score;
public string commands;
public string message;
}
public Rewards rewards;
public class Rewards {
public Dictionary<string, RewardInfo> RewardInfo = new Dictionary<string, RewardInfo>();
}
public class RewardInfo {
public bool IssueAtWipe = true;
public bool WaitUntilPlayerIsOnline = false;
public string First = "";
public string FirstMessage = "You received a reward for ranking #1 in {category}";
public string Second = "";
public string SecondMessage = "You received a reward for ranking #2 in {category}";
public string Third = "";
public string ThirdMessage = "You received a reward for ranking #3 in {category}";
}
public class DataStorage {
public Dictionary<ulong, Dictionary<string, object>> PlayerRankData = new Dictionary<ulong, Dictionary<string, object>>();
public Dictionary<DateTime, Dictionary<string, LeaderBoardData>> leaderBoards = new Dictionary<DateTime, Dictionary<string, LeaderBoardData>>();
}
public class LeaderBoardData {
public ulong UserID;
public string UserName;
public double Score;
}
public readonly Dictionary<string, object> PRDATA = new Dictionary<string, object>() {
{
"PrintToChat", true
}
, {
"Admin", false
}
, {
"OptOut", false
}
, {
"Changed", true
}
, {
"UserID", (ulong)0
}
, {
"Name", ""
}
, {
"Clan", "None"
}
, {
"TimePlayed", "0"
}
, {
"Status", "offline"
}
, {
"Economics", 0
}
, {
"ServerRewards", 0
}
, {
"ActiveDate", new DateTime()
}
, {
"PVPKills", 0
}
, {
"PVPDistance", 0.0
}
, {
"PVEKills", 0
}
, {
"PVEDistance", 0.0
}
, {
"NPCKills", 0
}
, {
"NPCDistance", 0.0
}
, {
"SleepersKilled", 0
}
, {
"HeadShots", 0
}
, {
"Deaths", 0
}
, {
"Suicides", 0
}
, {
"KDR", 0.0
}
, {
"SDR", 0.0
}
, {
"SkullsCrushed", 0
}
, {
"TimesWounded", 0
}
, {
"TimesHealed", 0
}
, {
"HeliHits", 0
}
, {
"HeliKills", 0
}
, {
"APCHits", 0
}
, {
"APCKills", 0
}
, {
"BarrelsDestroyed", 0
}
, {
"ExplosivesThrown", 0
}
, {
"ArrowsFired", 0
}
, {
"BulletsFired", 0
}
, {
"RocketsLaunched", 0
}
, {
"MLRSFired", 0
}
, {
"WeaponTrapsDestroyed", 0
}
, {
"DropsLooted", 0
}
, {
"FishCaught", 0
}
, {
"PlantsGathered", 0
}
, //intense options
{
"StructuresBuilt", 0
}
, {
"StructuresDemolished", 0
}
, {
"ItemsDeployed", 0
}
, {
"ItemsCrafted", 0
}
, {
"EntitiesRepaired", 0
}
, {
"ResourcesGathered", 0
}
, {
"StructuresUpgraded", 0
}
}
;
class WoundedData {
public float distance;
public ulong attackerId;
}
DataStorage data;
private Dictionary<ulong, Dictionary<string, object>> LiveData() => data.PlayerRankData;
private DynamicConfigFile PRData;
#endregion
#region SetupTakeDown
void Init() {
lang.RegisterMessages(Messages, this);
permission.RegisterPermission(permExcludeFromStats, this);
permission.RegisterPermission(permExcludedFromStats, this);
permission.RegisterPermission(permAdmin, this);
permission.RegisterPermission(permUse, this);
permission.RegisterPermission(permOptOut, this);
}
void OnServerInitialized() {
LoadConfigVariables();
TitleList = TitleList.OrderBy(x => conf.CategorySettings[x.Key].ChatPriority).ToDictionary(pair => pair.Key, pair => pair.Value);
foreach (var entry in conf.Options.chatCommandAliases) cmd.AddChatCommand($"{entry}", this, "CmdTarget");
Sql_conn = Sql.OpenDb(conf.MySQL.sql_host, conf.MySQL.sql_port, conf.MySQL.sql_db, conf.MySQL.sql_user, conf.MySQL.sql_pass + ";Connection Timeout = 10; CharSet=utf8mb4", this);
CheckDependencies();
SetUp(true);
dr = Interface.Oxide.DataFileSystem.ReadObject<DelayRewards>("PlayerRanksDelayedRewards") ?? new DelayRewards();
if (ServerRewards) timer.Repeat(600, 0, () => {
foreach (var entry in data.PlayerRankData) entry.Value["ServerRewards"] = ServerRewards?.Call("CheckPoints", entry.Key);
}
);
foreach (var lb in data.leaderBoards.ToDictionary(pair => pair.Key, pair => pair.Value)) foreach (var entry in PRDATA) if (!lb.Value.ContainsKey(entry.Key)) data.leaderBoards[lb.Key].Add(entry.Key, new LeaderBoardData {
UserID = 0, UserName = "No Record", Score = 0
}
);
if (newsave) DoNewMap();
loaded = true;
LoadTitles();
}
void DoNewMap() {
timer.Once(10f, () => {
if (conf.Options.IssueRewardsAtWipe) {
dr.DR.Clear();
IssueRewards(true, null);
}
if (conf.Options.TakeLBSnapShotAtWipe) SaveLB();
if (!conf.Options.WipeOnNewMap) return;
ClearData();
SetUp(true);
if (conf.MySQL.useMySQL) LoadMySQL(true);
}
);
}
void ClearData() {
data.PlayerRankData.Clear();
PRData.WriteObject(data);
foreach (var entry in TitleList.ToDictionary(val => val.Key, val => val.Value)) TitleList[entry.Key] = new Top();
LoadTitles();
}
void CheckDependencies() {
if (Friends == null && !conf.Options.record_FriendsAPI_Kills) PrintWarning(lang.GetMessage("noFriends", this));
if (Clans == null && (!conf.Options.record_ClanMate_Kills || conf.Options.displayClanStats)) {
PrintWarning(lang.GetMessage("noClans", this));
conf.Options.record_ClanMate_Kills = true;
SaveConfig(conf);
}
lib = Interface.GetMod().GetLibrary<Library>("RustIO");
if (conf.Options.record_RustIO_Friend_Kills && (lib == null || (isInstalled = lib.GetFunction("IsInstalled")) == null || (hasFriend = lib.GetFunction("HasFriend")) == null)) PrintWarning(lang.GetMessage("noRustio", this));
if (PlaytimeTracker == null) PrintWarning(lang.GetMessage("noPTT", this));
if (Economics == null) PrintWarning(lang.GetMessage("noEconomics", this));
if (ServerRewards == null) PrintWarning(lang.GetMessage("noServerRewards", this));
;
}
void Unload() {
if (!loaded) return;
Sql_conn?.Con?.Dispose();
foreach (BasePlayer player in BasePlayer.activePlayerList) DestroyMenu(String.Empty, player);
SaveData(false);
foreach (var cat in conf.CategorySettings) {
if (permission.GroupExists(cat.Key)) foreach (string member in permission.GetUsersInGroup(cat.Key).ToList()) permission.RemoveUserGroup(member, cat.Key);
if (conf.Titles.DestroyGroupsOnUnload) permission.RemoveGroup(cat.Key);
}
permission.SaveData();
}
bool newsave = false;
void OnNewSave(string filename) => newsave = true;
void OnPlayerDisconnected(BasePlayer player) {
if (ServerUsers.Is(player.userID, ServerUsers.UserGroup.Banned)) return;
//if (!data.PlayerRankData.ContainsKey(player.userID)) //// removed 2.3.3
OnPlayerConnected(player);
data.PlayerRankData[player.userID]["Status"] = "offline";
data.PlayerRankData[player.userID]["Changed"] = true;
DestroyMenu(String.Empty, player);
}
Timer SaveTimer;
void SetUp(bool offline) {
PRData = Interface.Oxide.DataFileSystem.GetFile("PlayerRanks");
LoadData();
List<ulong> online = new List<ulong>();
foreach (BasePlayer player in BasePlayer.activePlayerList) {
OnPlayerConnected(player);
online.Add(player.userID);
}
if (offline) foreach (var entry in data.PlayerRankData.Where(x => !online.Contains(x.Key))) UpdateOfflinePlayer(entry.Key);
SaveTimer?.Destroy();
SaveTimer = timer.Every(Mathf.Max(1, conf.Options.saveTimer) * 60, () => {
SaveData(true);
PrintWarning(lang.GetMessage("save", this));
}
);
SetUpBroadcast();
LoadTitles();
SetupRewards();
}
void SetupRewards() {
rewards = Interface.Oxide.DataFileSystem.ReadObject<Rewards>("PlayerRanksRewards");
foreach (var entry in CatsDict) if (!rewards.RewardInfo.ContainsKey(entry.Key)) rewards.RewardInfo.Add(entry.Key, new RewardInfo());
Interface.Oxide.DataFileSystem.WriteObject("PlayerRanksRewards", rewards);
}
void IssueRewards(bool newsave = false, string category = null) {
Dictionary<ulong, string> TopThree = new Dictionary<ulong, string>();
Dictionary<ulong, Dictionary<string, object>> dictToUse = new Dictionary<ulong, Dictionary<string, object>>();
foreach (var entry in rewards.RewardInfo) {
if (category != null && entry.Key != category || entry.Value == new RewardInfo() || (newsave && !entry.Value.IssueAtWipe)) continue;
TopThree.Clear();
dictToUse.Clear();
dictToUse = data.PlayerRankData.Where(pair => !HasPermission(pair.Key.ToString(), permExcludedFromStats) && (bool)pair.Value["OptOut"] == false && (conf.Options.allowadmin == true || (bool)pair.Value["Admin"] == false)).ToDictionary(val => val.Key, val => val.Value);
dictToUse = dictToUse.OrderByDescending(pair => D(pair.Value[entry.Key])).Where(x => D(x.Value[entry.Key]) > 0).Take(3).ToDictionary(val => val.Key, val => val.Value);
foreach (var e in dictToUse) if (!TopThree.ContainsKey(e.Key) && !NoPlug(entry.Key)) TopThree[e.Key] = GetPlaytimeClock(entry.Key, D(e.Value[entry.Key]), true) + (entry.Key.Contains("Distance") ? "m" : "");
for (int i = 0; i < TopThree.Count; i++) {
var winner = TopThree.ElementAt(i);
if (entry.Value.WaitUntilPlayerIsOnline) {
if (BasePlayer.FindByID(winner.Key)) IssueReward(winner.Key, entry.Key, winner.Value, i == 0 ? entry.Value.First : i == 1 ? entry.Value.Second : entry.Value.Third, i == 0 ? entry.Value.FirstMessage : i == 1 ? entry.Value.SecondMessage : entry.Value.ThirdMessage);
else {
if (!dr.DR.ContainsKey(winner.Key)) dr.DR.Add(winner.Key, new List<DelayedRewards>());
dr.DR[winner.Key].Add(new DelayedRewards() {
category = entry.Key, score = winner.Value, commands = i == 0 ? entry.Value.First : i == 1 ? entry.Value.Second : entry.Value.Third, message = i == 0 ? entry.Value.FirstMessage : i == 1 ? entry.Value.SecondMessage : entry.Value.ThirdMessage,
}
);
;
}
}
else IssueReward(winner.Key, entry.Key, winner.Value, i == 0 ? entry.Value.First : i == 1 ? entry.Value.Second : entry.Value.Third, i == 0 ? entry.Value.FirstMessage : i == 1 ? entry.Value.SecondMessage : entry.Value.ThirdMessage);
}
}
}
void IssueReward(ulong user, string category, string score, string commands, string message) {
if (commands != string.Empty) {
commands = commands.Replace(", ", ",");
foreach (var command in commands.Split(',')) Server.Command(command.Replace("{id}", user.ToString()));
if (message != string.Empty) {
var player = BasePlayer.FindByID(user);
if (player != null) SendReply(player, message.Replace("{category}", category));
}
}
}
void SetUpBroadcast() {
if (conf.Options.useTimedTopList) {
if (BroadcastTimer != null) BroadcastTimer.Destroy();
Broadcast.Clear();
foreach (var cat in conf.CategorySettings) {
if (conf.CategorySettings[cat.Key].IncludeInChatBroadcast == true && !IntenseBlock(cat.Key)) Broadcast.Add(cat.Key);
}
if (Broadcast.Count != 0) BroadcastLooper(0);
}
}
void BroadcastLooper(int counter) {
var time = 10;
if (BroadcastMethod(Broadcast[counter])) time = conf.Options.TimedTopListTimer * 60;
counter++;
if (counter == Broadcast.Count) counter = 0;
BroadcastTimer = timer.Once(time, () => BroadcastLooper(counter));
}
void OnClanCreate(string clanName) => UpdateClans();
void OnClanUpdate(string clanName) => UpdateClans();
void OnClanDestroy(string clanName) => UpdateClans();
void UpdateClans() {
NextTick(() => {
foreach (var entry in data.PlayerRankData) {
var getClan = Clans?.CallHook("GetClanOf", entry.Key);
if (getClan != null) entry.Value["Clan"] = (string)getClan;
else entry.Value["Clan"] = "None";
}
}
);
}
void OnPlayerConnected(BasePlayer player) {
ulong id = player.userID.Get();
if (ServerUsers.Is(id, ServerUsers.UserGroup.Banned)) {
if (conf.Options.deleteOnBan && data.PlayerRankData.ContainsKey(id)) data.PlayerRankData.Remove(id);
return;
}
DestroyMenu(String.Empty, player);
if (!data.PlayerRankData.ContainsKey(id)) {
data.PlayerRankData.Add(id, new Dictionary<string, object>());
foreach (var entry in PRDATA) data.PlayerRankData[id].Add(entry.Key, entry.Value);
}
UpdatePlayer(player);
GiveDelayedRewards(player);
}
public void GiveDelayedRewards(BasePlayer player) {
if (dr == null || player.IsReceivingSnapshot || player.IsDead()) {
timer.Once(1f, () => {
if (player?.net?.connection != null) GiveDelayedRewards(player);
}
);
return;
}
List<DelayedRewards> drs;
if (dr.DR.TryGetValue(player.userID, out drs)) {
foreach (var entry in drs) IssueReward(player.userID, entry.category, entry.score, entry.commands, entry.message);
dr.DR.Remove(player.userID);
}
}
public void UpdateOfflinePlayer(ulong userID) {
var path = data.PlayerRankData[userID];
bool gotClan = (Clans?.CallHook("GetClanOf", userID) != null);
var time = PlaytimeTracker?.Call("GetPlayTime", userID.ToString());
path["Status"] = "offline";
path["Economics"] = Economics ? Economics?.Call("Balance", userID) : path["Economics"];
path["ServerRewards"] = ServerRewards ? ServerRewards?.Call("CheckPoints", userID) : path["ServerRewards"];
path["Clan"] = gotClan ? (string)Clans?.CallHook("GetClanOf", userID) : "None";
path["TimePlayed"] = (time == null) ? "0" : time;
}
public void UpdatePlayer(BasePlayer player) {
var path = data.PlayerRankData[player.userID];
var clan = Clans?.CallHook("GetClanOf", player.userID.Get());
var time = PlaytimeTracker?.Call("GetPlayTime", player.UserIDString);
path["UserID"] = player.userID.Get();
path["Admin"] = IsAuth(player);
path["Changed"] = true;
path["Name"] = CleanString(player.displayName, "");
path["Status"] = "online";
path["ActiveDate"] = DateTime.UtcNow;
path["Economics"] = Economics ? Economics?.Call("Balance", player.userID.Get()) : path["Economics"];
path["ServerRewards"] = ServerRewards ? ServerRewards?.Call("CheckPoints", player.userID.Get()) : path["ServerRewards"];
path["Clan"] = clan != null ? (string)clan : "None";
path["TimePlayed"] = (time == null) ? "0" : time;
path["OptOut"] = HasPermission(player.UserIDString, permOptOut);
DoTitle(player.userID, "TimePlayed");
//// Added in 2.3.3 - Needs tested.
}
void OnEconomicsBalanceUpdated(string userID, double amount) {
var id = Convert.ToUInt64(userID);
if (Economics && data.PlayerRankData.ContainsKey(id)) {
data.PlayerRankData[id]["Economics"] = Economics?.Call("Balance", id);
DoTitle(Convert.ToUInt64(userID), "Economics");
}
}
void OnSrChanged(ulong userID, double amount) //Doesn't exist
{
if (ServerRewards && data.PlayerRankData.ContainsKey(userID)) {
data.PlayerRankData[userID]["ServerRewards"] = ServerRewards?.Call("CheckPoints", (ulong)userID);
DoTitle(userID, "ServerRewards");
}
}
private string GetPlaytimeClock(string name, double time, bool friendly) {
if (name.Contains("TimePlayed")) {
TimeSpan dateDifference = TimeSpan.FromSeconds((float)time);
var days = dateDifference.Days.ToString("00");
var hours = friendly ? dateDifference.Hours.ToString("0") : dateDifference.Hours.ToString("00");
var mins = friendly ? dateDifference.Minutes.ToString("0") : dateDifference.Minutes.ToString("00");
var secs = dateDifference.Seconds.ToString("00");
if (conf.Options.PlayTime_HH_MM) {
hours = friendly ? Math.Floor(dateDifference.TotalHours).ToString("0") : Math.Floor(dateDifference.TotalHours).ToString("00");
return string.Format("{0}:{1}", hours, mins);
}
if (friendly) {
if (days == "00" && hours == "00") return string.Format("{0}:{1}", mins, secs);
if (days == "00") return string.Format("{0}:{1}:{2}", hours, mins, secs);
}
return string.Format("{0}:{1}:{2}:{3}", days, hours, mins, secs);
}
return time.ToString();
}
#endregion
#region Hooks
void OnPlayerBanned(string name, ulong id, string address, string reason) {
if (conf.Options.deleteOnBan) data.PlayerRankData.Remove(id);
}
void OnEntityTakeDamage(BaseEntity entity, HitInfo hitinfo) {
if (!loaded || hitinfo == null || entity?.net?.ID == null || entity.IsDestroyed) return;
var player = hitinfo?.InitiatorPlayer;
if (player == null) return;
if (conf.Options.blockEvents && CheckEvents(player)) return;
if (entity is PatrolHelicopter) {
if (!HeliAttackers.ContainsKey(entity.net.ID.Value)) HeliAttackers.Add(entity.net.ID.Value, new Dictionary<ulong, int>());
if (!HeliAttackers[entity.net.ID.Value].ContainsKey(player.userID)) HeliAttackers[entity.net.ID.Value].Add(player.userID, 1);
else {
HeliAttackers[entity.net.ID.Value][player.userID]++;
Plus(player, "HeliHits", 1);
}
}
if (entity is BradleyAPC) {
float amount = hitinfo.damageTypes?.Total() ?? 0;
if (amount > 0) {
if (!BradleyAttackers.ContainsKey(entity.net.ID.Value)) //explosive ammo does get this far, because two damage types are processed.
BradleyAttackers.Add(entity.net.ID.Value, new Dictionary<ulong, float>());
if (!BradleyAttackers[entity.net.ID.Value].ContainsKey(player.userID)) BradleyAttackers[entity.net.ID.Value].Add(player.userID, amount);
else {
BradleyAttackers[entity.net.ID.Value][player.userID] += amount;
Plus(player, "APCHits", 1);
}
}
}
var playerEntity = entity as BasePlayer;
if (playerEntity == null || (conf.Options.PVPHeadshotsOnly && playerEntity.IsNpc)) return;
if (hitinfo.isHeadshot && !FriendCheck(player, playerEntity) && !playerEntity.IsSleeping()) {
var health = entity.Health();
NextTick(() => {
if (conf.Options.CountHeadshotsOnlyIfKill) {
if (entity == null || entity.Health() < 0.5f) Plus(player, "HeadShots", 1);
}
else {
if (entity == null || entity.Health() < health - 1f) Plus(player, "HeadShots", 1);
}
}
);
}
}
private ulong GetMajorityAttacker(ulong id) {
if (HeliAttackers.ContainsKey(id)) return HeliAttackers[id].OrderByDescending(pair => pair.Value).First().Key;
if (BradleyAttackers.ContainsKey(id)) return BradleyAttackers[id].OrderByDescending(pair => pair.Value).First().Key;
return 0U;
}
void OnEntityDeath(BaseEntity entity, HitInfo hitinfo) {
if (entity == null || entity.name.Contains("corpse")) return;
var victim = entity as BasePlayer;
bool npcVic = victim != null && (victim.IsNpc || victim.Categorize() == "Zombie" || IsHumanNPC(victim));
var attacker = hitinfo?.Initiator as BasePlayer;
if (attacker == null && victim != null) {
if (woundedData.ContainsKey(victim.userID)) {
attacker = BasePlayer.FindByID(woundedData[victim.userID].attackerId);
if (attacker == null || !data.PlayerRankData.ContainsKey(attacker.userID)) return;
if (conf.Options.blockEvents) if (CheckEvents(attacker)) return;
var distance = woundedData[victim.userID].distance;
if (npcVic) {
data.PlayerRankData[attacker.userID]["NPCKills"] = D(data.PlayerRankData[attacker.userID]["NPCKills"]) + 1;
if (distance > D(data.PlayerRankData[attacker.userID]["NPCDistance"])) data.PlayerRankData[attacker.userID]["NPCDistance"] = Math.Round(distance, 2);
}
else {
data.PlayerRankData[attacker.userID]["PVPKills"] = D(data.PlayerRankData[attacker.userID]["PVPKills"]) + 1;
if (distance > D(data.PlayerRankData[attacker.userID]["PVPDistance"])) data.PlayerRankData[attacker.userID]["PVPDistance"] = Math.Round(distance, 2);
ProcessDeath(victim);
}
woundedData.Remove(victim.userID);
}
else if (!npcVic) {
string[] stringArray = {
"Cold", "Drowned", "Heat", "Suicide", "Generic", "Posion", "Radiation", "Thirst", "Hunger", "Fall"
}
;
if (stringArray.Any(victim.lastDamage.ToString().Contains)) ProcessSuicide(victim);
ProcessDeath(victim);
}
return;
}
if (entity is PatrolHelicopter) {
BasePlayer plyr = BasePlayer.FindByID(GetMajorityAttacker(entity.net.ID.Value));
if (plyr != null) Plus(plyr, "HeliKills", 1);
HeliAttackers.Remove(entity.net.ID.Value);
return;
}
if (entity is BradleyAPC) {
BasePlayer plyr = BasePlayer.FindByID(GetMajorityAttacker(entity.net.ID.Value));
if (plyr != null) Plus(plyr, "APCKills", 1);
BradleyAttackers.Remove(entity.net.ID.Value);
return;
}
if (attacker != null) {
bool explosion = hitinfo.damageTypes.GetMajorityDamageType() == DamageType.Explosion || hitinfo.damageTypes.GetMajorityDamageType() == DamageType.Heat;
if (!attacker.IsNpc && attacker.Categorize() != "Zombie" && !IsHumanNPC(attacker)) {
if (entity is SimpleShark || entity is WildlifeHazard || entity is BaseNPC2 || entity.PrefabName.Contains("agents/") && !(entity is NPCPlayer)) PlusKill(attacker, entity, "PVEKills", "PVEDistance", 1, explosion);
else if (entity.name.Contains("barrel")) Plus(attacker, "BarrelsDestroyed", 1);
else if (victim == null && (entity.name.Contains("turret") || entity.name.Contains("guntrap"))) Plus(attacker, "WeaponTrapsDestroyed", 1);
else if (victim != null) {
if (attacker == victim) ProcessSuicide(attacker);
else if (npcVic) PlusKill(attacker, victim, "NPCKills", "NPCDistance", 1, explosion);
else {
if (victim.IsSleeping()) {
if (conf.Options.PVPKillsCountsSleeperKills) ProcessPVPKill(attacker, victim);
PlusKill(attacker, victim, "SleepersKilled", "PVPDistance", 1, explosion);
}
else ProcessPVPKill(attacker, victim);
}
}
}
}
if (victim == null) return;
if (!npcVic) ProcessDeath(victim);
if (woundedData.ContainsKey(victim.userID)) woundedData.Remove(victim.userID);
}
public bool IsHumanNPC(BasePlayer player) {
foreach (var comp in player?.GetComponents<Component>()) if (comp?.GetType()?.Name == "HumanPlayer") return true;
return false;
}
void OnExplosiveThrown(BasePlayer player, BaseEntity exp, ThrownWeapon item) {
if (exp != null && !(exp is SupplySignal)) Plus(player, "ExplosivesThrown", 1);
}
void OnWeaponFired(BaseProjectile projectile, BasePlayer player, ItemModProjectile mod) {
if (mod.ToString().Contains("arrow")) //Check ammo type instead
Plus(player, "ArrowsFired", 1);
if (mod.ToString().Contains("ammo")) Plus(player, "BulletsFired", 1);
}
void OnRocketLaunched(BasePlayer player) {
var held = player.GetHeldEntity();
if (held != null && held.ShortPrefabName.Contains("mgl") && !conf.Options.Include_MGL_In_RocketsLaunched) return;
Plus(player, "RocketsLaunched", 1);
}
void OnEntityBuilt(Planner plan, GameObject objectBlock) {
if (conf.Options.useIntenseOptions) {
BasePlayer player = plan?.GetOwnerPlayer();
if (player == null) return;
if (player.GetActiveItem()?.info?.displayName?.english == "Building Plan") Plus(player, "StructuresBuilt", 1);
else Plus(player, "ItemsDeployed", 1);
}
}
void OnStructureDemolish(BaseCombatEntity entity, BasePlayer player) => Plus(player, "StructuresDemolished", 1);
void OnItemCraft(ItemCraftTask item, BasePlayer crafter) {
if (conf.Options.useIntenseOptions && crafter != null) Plus(crafter, "ItemsCrafted", item.amount);
}
void OnStructureRepair(BaseCombatEntity entity, BasePlayer player) {
if (conf.Options.useIntenseOptions) Plus(player, "EntitiesRepaired", 1);
}
void OnHealingItemUse(HeldEntity item, BasePlayer target) {
if (target?.net?.connection == null) return;
Plus(target, "TimesHealed", 1);
}
void OnItemUse(Item item) {
BasePlayer player = item?.GetOwnerPlayer();
if (player?.net?.connection == null) return;
if (item.GetOwnerPlayer() == null) return;
if (player != null && item.info.displayName.english == "Large Medkit") Plus(player, "TimesHealed", 1);
if (item.info.shortname != "skull.human" || item.name == null || !item.name.Contains("Skull of")) return;
if (!player.displayName.Contains($"{item.name.Substring(10, item.name.Length - 11)}")) Plus(player, "SkullsCrushed", 1);
}
void CanBeWounded(BasePlayer player, HitInfo hitInfo) {
if (player == null || hitInfo == null) return;
if (player.net?.connection == null) return;
var attacker = hitInfo.InitiatorPlayer;
if (attacker != null) {
if (attacker == player || FriendCheck(player, attacker)) return;
woundedData[player.userID] = new WoundedData {
distance = Vector3.Distance(player.transform.position, attacker.transform.position), attackerId = attacker.userID
}
;
NextTick(() => {
if (player.IsWounded()) Plus(player, "TimesWounded", 1);
}
);
}
}
void OnPlayerRecover(BasePlayer player) {
if (woundedData.ContainsKey(player.userID)) woundedData.Remove(player.userID);
}
void OnStructureUpgrade(BaseCombatEntity entity, BasePlayer player, BuildingGrade.Enum grade) => Plus(player, "StructuresUpgraded", 1);
void OnDispenserBonus(ResourceDispenser d, BaseEntity e, Item i) => OnDispenserGather(d, e, i);
void OnDispenserGather(ResourceDispenser dispenser, BaseEntity entity, Item item) {
if (entity == null || item == null) return;
BasePlayer player = entity.ToPlayer();
if (player != null) UpCollect(player, item.amount);
}
void OnGrowableGathered(GrowableEntity plant, Item item, BasePlayer p) {
if (plant != null && p != null && item != null) {
if (plant?.planter == null && conf.Options.CountPlantsOnlyFromPlanters) return;
Plus(p, "PlantsGathered", item.amount);
}
}
List<ulong> UsedIDs = new List<ulong>();
void OnCollectiblePickup(CollectibleEntity item, BasePlayer player) {
if (item?.net?.ID == null || item.itemList == null || item.itemList[0] == null) return;
if (UsedIDs.Contains(item.net.ID.Value)) return;
UsedIDs.Add(item.net.ID.Value);
UpCollect(player, (int)item.itemList[0].amount);
}
void UpCollect(BasePlayer p, int amount) {
if (!conf.Options.useIntenseOptions || amount < 1) return;
NextTick(() => {
if (p != null) Plus(p, "ResourcesGathered", amount);
}
);
}
void OnFishCatch(Item item, BaseFishingRod rod, BasePlayer player) {
if (player == null || item?.info?.shortname == null) return;
if (item?.info?.category == ItemCategory.Food && !item.info.shortname.Contains("water")) Plus(player, "FishCaught", 1);
}
void OnMlrsFired(MLRS mlrs, BasePlayer player) => Plus(player, "MLRSFired", 1);
void OnEntitySpawned(SupplyDrop entity) {
NextTick(() => {
if (entity != null) airdrops.Add(entity.net.ID.Value);
}
);
}
void OnLootEntity(BasePlayer player, SupplyDrop entity) {
if (player == null || entity?.net?.ID == null) return;
if (airdrops.Contains(entity.net.ID.Value)) {
airdrops.Remove(entity.net.ID.Value);
Plus(player, "DropsLooted", 1);
}
}
#endregion
#region Processes
bool ProcessChecks(BasePlayer player) {
if (!loaded) return false;
if ((!conf.Options.statCollection) || (conf.Options.blockEvents && CheckEvents(player))) return false;
return data.PlayerRankData.ContainsKey(player.userID);
}
bool FriendCheck(BasePlayer player, BasePlayer victim) {
if (player?.net?.connection == null || victim?.net?.connection == null) return false;
if (Clans && !conf.Options.record_ClanMate_Kills) if (IsClanmate(player.userID, victim.userID)) return true;
if (Friends && !conf.Options.record_FriendsAPI_Kills) if (IsFriend(player.userID, victim.us
// ... [Code preview truncated due to length. Full source in attached file] ...