From 6392b9e868fbbb71485c12ac4a3fa60bdc5c0391 Mon Sep 17 00:00:00 2001 From: Nicholas Westerhausen <2317381+nwesterhausen@users.noreply.github.com> Date: Tue, 26 Oct 2021 16:16:15 -0400 Subject: [PATCH 01/15] first pass of using litedb --- DiscordConnector.csproj | 2 +- src/Patches/ChatPatches.cs | 6 +- src/Patches/ZNetPatches.cs | 190 +++++++++++++-------------- src/Plugin.cs | 8 +- src/Records.cs | 211 ++++++++++++++++++++---------- src/Records/Classes/SimpleStat.cs | 67 ++++++++++ src/Records/Database.cs | 154 ++++++++++++++++++++++ 7 files changed, 470 insertions(+), 168 deletions(-) create mode 100644 src/Records/Classes/SimpleStat.cs create mode 100644 src/Records/Database.cs diff --git a/DiscordConnector.csproj b/DiscordConnector.csproj index 6db13cb..b2d430b 100644 --- a/DiscordConnector.csproj +++ b/DiscordConnector.csproj @@ -35,7 +35,7 @@ - + diff --git a/src/Patches/ChatPatches.cs b/src/Patches/ChatPatches.cs index 4214da4..1860ccb 100644 --- a/src/Patches/ChatPatches.cs +++ b/src/Patches/ChatPatches.cs @@ -27,7 +27,7 @@ private static void Prefix(ref GameObject go, ref long senderID, ref Vector3 pos } if (Plugin.StaticConfig.StatsPingEnabled) { - Plugin.StaticRecords.Store(RecordCategories.Ping, user, 1); + Plugin.StaticDatabase.InsertPingRecord(user, senderID, pos); } if (Plugin.StaticConfig.ChatPingEnabled) { @@ -64,7 +64,7 @@ private static void Prefix(ref GameObject go, ref long senderID, ref Vector3 pos } if (Plugin.StaticConfig.StatsJoinEnabled) { - Plugin.StaticRecords.Store(RecordCategories.Join, user, 1); + Plugin.StaticDatabase.InsertJoinRecord(user, senderID, pos); } if (Plugin.StaticConfig.PlayerJoinMessageEnabled) { @@ -99,7 +99,7 @@ private static void Prefix(ref GameObject go, ref long senderID, ref Vector3 pos } if (Plugin.StaticConfig.StatsShoutEnabled) { - Plugin.StaticRecords.Store(RecordCategories.Shout, user, 1); + Plugin.StaticDatabase.InsertShoutRecord(user, senderID, pos); } if (Plugin.StaticConfig.ChatShoutEnabled) { diff --git a/src/Patches/ZNetPatches.cs b/src/Patches/ZNetPatches.cs index 940963c..133d301 100644 --- a/src/Patches/ZNetPatches.cs +++ b/src/Patches/ZNetPatches.cs @@ -61,50 +61,50 @@ private static void Postfix(ZRpc rpc, ZDOID characterID) } if (Plugin.StaticConfig.PlayerDeathMessageEnabled) { - if (Plugin.StaticConfig.AnnouncePlayerFirstDeathEnabled && Plugin.StaticRecords.Retrieve(RecordCategories.Death, peer.m_playerName) == 0) - { - string firstDeathMessage = MessageTransformer.FormatPlayerMessage(Plugin.StaticConfig.PlayerFirstDeathMessage, peer.m_playerName); - if (Plugin.StaticConfig.PlayerDeathPosEnabled) - { - if (Plugin.StaticConfig.DiscordEmbedsEnabled || !firstDeathMessage.Contains("%POS%")) - { - DiscordApi.SendMessage(firstDeathMessage, peer.m_refPos); - } - else - { - DiscordApi.SendMessage(MessageTransformer.FormatPlayerMessage(Plugin.StaticConfig.PlayerFirstDeathMessage, peer.m_playerName, peer.m_refPos)); - } - } - else - { - DiscordApi.SendMessage(firstDeathMessage); - } + if (Plugin.StaticConfig.AnnouncePlayerFirstDeathEnabled && Plugin.StaticRecords.Retrieve(RecordCategories.Death, peer.m_playerName) == 0) + { + string firstDeathMessage = MessageTransformer.FormatPlayerMessage(Plugin.StaticConfig.PlayerFirstDeathMessage, peer.m_playerName); + if (Plugin.StaticConfig.PlayerDeathPosEnabled) + { + if (Plugin.StaticConfig.DiscordEmbedsEnabled || !firstDeathMessage.Contains("%POS%")) + { + DiscordApi.SendMessage(firstDeathMessage, peer.m_refPos); + } + else + { + DiscordApi.SendMessage(MessageTransformer.FormatPlayerMessage(Plugin.StaticConfig.PlayerFirstDeathMessage, peer.m_playerName, peer.m_refPos)); + } + } + else + { + DiscordApi.SendMessage(firstDeathMessage); + } } - else - { - string message = MessageTransformer.FormatPlayerMessage(Plugin.StaticConfig.DeathMessage, peer.m_playerName); - if (Plugin.StaticConfig.PlayerDeathPosEnabled) - { - if (Plugin.StaticConfig.DiscordEmbedsEnabled || !message.Contains("%POS%")) - { - DiscordApi.SendMessage(message, peer.m_refPos); - } - else - { - message = MessageTransformer.FormatPlayerMessage(Plugin.StaticConfig.DeathMessage, peer.m_playerName, peer.m_refPos); - DiscordApi.SendMessage(message); - } - } - else - { - DiscordApi.SendMessage(message); + else + { + string message = MessageTransformer.FormatPlayerMessage(Plugin.StaticConfig.DeathMessage, peer.m_playerName); + if (Plugin.StaticConfig.PlayerDeathPosEnabled) + { + if (Plugin.StaticConfig.DiscordEmbedsEnabled || !message.Contains("%POS%")) + { + DiscordApi.SendMessage(message, peer.m_refPos); + } + else + { + message = MessageTransformer.FormatPlayerMessage(Plugin.StaticConfig.DeathMessage, peer.m_playerName, peer.m_refPos); + DiscordApi.SendMessage(message); + } + } + else + { + DiscordApi.SendMessage(message); } } - } - + } + if (Plugin.StaticConfig.StatsDeathEnabled) { - Plugin.StaticRecords.Store(RecordCategories.Death, peer.m_playerName, 1); + Plugin.StaticDatabase.InsertDeathRecord(peer.m_playerName, peer.m_uid, peer.m_refPos); } } else @@ -114,26 +114,26 @@ private static void Postfix(ZRpc rpc, ZDOID characterID) Plugin.StaticLogger.LogDebug($"Added player {peer.m_uid} ({peer.m_playerName}) to joined player list."); if (Plugin.StaticConfig.PlayerJoinMessageEnabled) { - if (Plugin.StaticConfig.AnnouncePlayerFirstJoinEnabled && Plugin.StaticRecords.Retrieve(RecordCategories.Join, peer.m_playerName) == 0) - { - string firstJoinMessage = MessageTransformer.FormatPlayerMessage(Plugin.StaticConfig.PlayerFirstJoinMessage, peer.m_playerName); - if (Plugin.StaticConfig.PlayerJoinPosEnabled) - { - if (Plugin.StaticConfig.DiscordEmbedsEnabled || !firstJoinMessage.Contains("%POS%")) - { - DiscordApi.SendMessage(firstJoinMessage, peer.m_refPos); - } - else - { - firstJoinMessage = MessageTransformer.FormatPlayerMessage(Plugin.StaticConfig.JoinMessage, peer.m_playerName, peer.m_refPos); - DiscordApi.SendMessage(firstJoinMessage); - } - } - else - { - DiscordApi.SendMessage( - MessageTransformer.FormatPlayerMessage(Plugin.StaticConfig.PlayerFirstJoinMessage, peer.m_playerName)); - } + if (Plugin.StaticConfig.AnnouncePlayerFirstJoinEnabled && Plugin.StaticRecords.Retrieve(RecordCategories.Join, peer.m_playerName) == 0) + { + string firstJoinMessage = MessageTransformer.FormatPlayerMessage(Plugin.StaticConfig.PlayerFirstJoinMessage, peer.m_playerName); + if (Plugin.StaticConfig.PlayerJoinPosEnabled) + { + if (Plugin.StaticConfig.DiscordEmbedsEnabled || !firstJoinMessage.Contains("%POS%")) + { + DiscordApi.SendMessage(firstJoinMessage, peer.m_refPos); + } + else + { + firstJoinMessage = MessageTransformer.FormatPlayerMessage(Plugin.StaticConfig.JoinMessage, peer.m_playerName, peer.m_refPos); + DiscordApi.SendMessage(firstJoinMessage); + } + } + else + { + DiscordApi.SendMessage( + MessageTransformer.FormatPlayerMessage(Plugin.StaticConfig.PlayerFirstJoinMessage, peer.m_playerName)); + } } string message = MessageTransformer.FormatPlayerMessage(Plugin.StaticConfig.JoinMessage, peer.m_playerName); if (Plugin.StaticConfig.PlayerJoinPosEnabled) @@ -142,21 +142,21 @@ private static void Postfix(ZRpc rpc, ZDOID characterID) { DiscordApi.SendMessage(message, peer.m_refPos); } - else - { - message = MessageTransformer.FormatPlayerMessage(Plugin.StaticConfig.JoinMessage, peer.m_playerName, peer.m_refPos); - DiscordApi.SendMessage(message); - } + else + { + message = MessageTransformer.FormatPlayerMessage(Plugin.StaticConfig.JoinMessage, peer.m_playerName, peer.m_refPos); + DiscordApi.SendMessage(message); + } } else { DiscordApi.SendMessage(message); } - } - + } + if (Plugin.StaticConfig.StatsJoinEnabled) { - Plugin.StaticRecords.Store(RecordCategories.Join, peer.m_playerName, 1); + Plugin.StaticDatabase.InsertJoinRecord(peer.m_playerName, peer.m_uid, peer.m_refPos); } } } @@ -172,25 +172,25 @@ private static void Prefix(ZRpc rpc) { if (Plugin.StaticConfig.PlayerLeaveMessageEnabled) { - if (Plugin.StaticConfig.AnnouncePlayerFirstLeaveEnabled && Plugin.StaticRecords.Retrieve(RecordCategories.Leave, peer.m_playerName) == 0) - { - string firstLeaveMessage = MessageTransformer.FormatPlayerMessage(Plugin.StaticConfig.PlayerFirstLeaveMessage, peer.m_playerName); - if (Plugin.StaticConfig.PlayerLeavePosEnabled) - { - if (Plugin.StaticConfig.DiscordEmbedsEnabled || !firstLeaveMessage.Contains("%POS%")) - { - DiscordApi.SendMessage(firstLeaveMessage, peer.m_refPos); - } - else - { - firstLeaveMessage = MessageTransformer.FormatPlayerMessage(Plugin.StaticConfig.PlayerFirstLeaveMessage, peer.m_playerName, peer.m_refPos); - DiscordApi.SendMessage(firstLeaveMessage); - } - } - else - { - DiscordApi.SendMessage(firstLeaveMessage); - } + if (Plugin.StaticConfig.AnnouncePlayerFirstLeaveEnabled && Plugin.StaticRecords.Retrieve(RecordCategories.Leave, peer.m_playerName) == 0) + { + string firstLeaveMessage = MessageTransformer.FormatPlayerMessage(Plugin.StaticConfig.PlayerFirstLeaveMessage, peer.m_playerName); + if (Plugin.StaticConfig.PlayerLeavePosEnabled) + { + if (Plugin.StaticConfig.DiscordEmbedsEnabled || !firstLeaveMessage.Contains("%POS%")) + { + DiscordApi.SendMessage(firstLeaveMessage, peer.m_refPos); + } + else + { + firstLeaveMessage = MessageTransformer.FormatPlayerMessage(Plugin.StaticConfig.PlayerFirstLeaveMessage, peer.m_playerName, peer.m_refPos); + DiscordApi.SendMessage(firstLeaveMessage); + } + } + else + { + DiscordApi.SendMessage(firstLeaveMessage); + } } string message = MessageTransformer.FormatPlayerMessage(Plugin.StaticConfig.LeaveMessage, peer.m_playerName); @@ -200,22 +200,22 @@ private static void Prefix(ZRpc rpc) { DiscordApi.SendMessage(message, peer.m_refPos); } - else - { - message = MessageTransformer.FormatPlayerMessage(Plugin.StaticConfig.LeaveMessage, peer.m_playerName, peer.m_refPos); - DiscordApi.SendMessage(message); - } - + else + { + message = MessageTransformer.FormatPlayerMessage(Plugin.StaticConfig.LeaveMessage, peer.m_playerName, peer.m_refPos); + DiscordApi.SendMessage(message); + } + } else { DiscordApi.SendMessage(message); } - } - + } + if (Plugin.StaticConfig.StatsLeaveEnabled) { - Plugin.StaticRecords.Store(RecordCategories.Leave, peer.m_playerName, 1); + Plugin.StaticDatabase.InsertLeaveRecord(peer.m_playerName, peer.m_uid, peer.m_refPos); } } } diff --git a/src/Plugin.cs b/src/Plugin.cs index 4c345e9..013c01a 100644 --- a/src/Plugin.cs +++ b/src/Plugin.cs @@ -1,5 +1,6 @@ using BepInEx; using BepInEx.Logging; +using DiscordConnector.Records; using HarmonyLib; using UnityEngine; using UnityEngine.Rendering; @@ -11,7 +12,8 @@ public class Plugin : BaseUnityPlugin { internal static ManualLogSource StaticLogger; internal static PluginConfig StaticConfig; - internal static Records StaticRecords; + internal static RecordsOld StaticRecords; + internal static Database StaticDatabase; internal static Leaderboard StaticLeaderboards; internal static EventWatcher StaticEventWatcher; internal static string PublicIpAddress; @@ -21,7 +23,8 @@ public Plugin() { StaticLogger = Logger; StaticConfig = new PluginConfig(Config); - StaticRecords = new Records(Paths.GameRootPath); + StaticRecords = new RecordsOld(Paths.GameRootPath); + StaticDatabase = new Records.Database(Paths.GameRootPath); StaticLeaderboards = new Leaderboard(); } @@ -62,6 +65,7 @@ private void Awake() private void OnDestroy() { _harmony.UnpatchSelf(); + StaticDatabase.Dispose(); } /// diff --git a/src/Records.cs b/src/Records.cs index d139d4b..aafc4f7 100644 --- a/src/Records.cs +++ b/src/Records.cs @@ -3,6 +3,7 @@ using System.IO; using System.Threading.Tasks; using Newtonsoft.Json; +using UnityEngine; namespace DiscordConnector { @@ -42,7 +43,7 @@ internal class Record public string Category { get; set; } public List Values { get; set; } } - class Records + class RecordsOld { private static string DEFAULT_FILENAME = "records.json"; private string storepath; @@ -55,7 +56,7 @@ class Records /// /// The directory to store the json file of records in. /// The name of the file to use for storage. - public Records(string basePath, string fileName = null) + public RecordsOld(string basePath, string fileName = null) { if (fileName == null) { @@ -70,84 +71,160 @@ public Records(string basePath, string fileName = null) } } - /// - /// Add to a record for under in the records database. - /// This will not save the record if the is not one defined in RecordCategories. - /// - /// RecordCategories category to store the value under - /// The player's name. - /// How much to increase current stored value by. - public void Store(string key, string playername, int value) + public void Store(string key, string playername, long steamId, Vector3 pos) { - if (Plugin.StaticConfig.CollectStatsEnabled) + switch (key) { - if (Array.IndexOf(RecordCategories.All, key) >= 0) - { - foreach (Record r in recordCache) - { - if (r.Category.Equals(key)) - { - bool stored = false; - foreach (RecordValue v in r.Values) - { - if (v.Key.Equals(playername)) - { - v.Value += value; - stored = true; - } - } - if (!stored) - { - r.Values.Add(new RecordValue() - { - Key = playername, - Value = value - }); - } - } - } - // After adding new data, flush data to disk. - FlushCache().ContinueWith( - t => Plugin.StaticLogger.LogWarning(t.Exception), - TaskContinuationOptions.OnlyOnFaulted); - } - else - { - Plugin.StaticLogger.LogWarning($"Unable to store record of {key} for player {playername} - not considered a valid category."); - } + case RecordCategories.Death: + Plugin.StaticDatabase.InsertDeathRecord(playername, steamId, pos); + break; + case RecordCategories.Join: + Plugin.StaticDatabase.InsertJoinRecord(playername, steamId, pos); + break; + case RecordCategories.Leave: + Plugin.StaticDatabase.InsertLeaveRecord(playername, steamId, pos); + break; + case RecordCategories.Ping: + Plugin.StaticDatabase.InsertPingRecord(playername, steamId, pos); + break; + case RecordCategories.Shout: + Plugin.StaticDatabase.InsertShoutRecord(playername, steamId, pos); + break; + default: + Plugin.StaticLogger.LogDebug($"Unable to store record, invalid key '{key}'"); + break; } } - /// - /// Get the value stored under at . - /// - /// The RecordCategories category the value is stored under - /// The name of the player - /// This will return 0 if there is no record found for that player. It will return -1 if the category is invalid. + public void Store(string key, string playername, long steamId) + { + Store(key, playername, steamId, Vector3.zero); + } + public int Retrieve(string key, string playername) { if (!Plugin.StaticConfig.CollectStatsEnabled) { return -1; } - if (Array.IndexOf(RecordCategories.All, key) >= 0) + switch (key) { - foreach (Record r in recordCache) - { - if (r.Category.Equals(key)) - { - foreach (RecordValue v in r.Values) - { - if (v.Key.Equals(playername)) - { - return v.Value; - } - } - } - } + case RecordCategories.Death: + return Plugin.StaticDatabase.GetNumberDeaths(playername); + case RecordCategories.Join: + return Plugin.StaticDatabase.GetNumberJoins(playername); + case RecordCategories.Leave: + return Plugin.StaticDatabase.GetNumberLeaves(playername); + case RecordCategories.Ping: + return Plugin.StaticDatabase.GetNumberPings(playername); + case RecordCategories.Shout: + return Plugin.StaticDatabase.GetNumberShouts(playername); + default: + Plugin.StaticLogger.LogDebug($"Unable to retrieve record, invalid key '{key}'"); + return -2; } - Plugin.StaticLogger.LogWarning($"No stored record for player {playername} under {key}, returning default of 0."); - return 0; } + public int Retrieve(string key, long steamId) + { + if (!Plugin.StaticConfig.CollectStatsEnabled) + { + return -1; + } + switch (key) + { + case RecordCategories.Death: + return Plugin.StaticDatabase.GetNumberDeaths(steamId); + case RecordCategories.Join: + return Plugin.StaticDatabase.GetNumberJoins(steamId); + case RecordCategories.Leave: + return Plugin.StaticDatabase.GetNumberLeaves(steamId); + case RecordCategories.Ping: + return Plugin.StaticDatabase.GetNumberPings(steamId); + case RecordCategories.Shout: + return Plugin.StaticDatabase.GetNumberShouts(steamId); + default: + Plugin.StaticLogger.LogDebug($"Unable to retrieve record, invalid key '{key}'"); + return -2; + } + } + + /// + /// Add to a record for under in the records database. + /// This will not save the record if the is not one defined in RecordCategories. + /// + /// RecordCategories category to store the value under + /// The player's name. + /// How much to increase current stored value by. + // public void Store(string key, string playername, int value) + // { + // if (Plugin.StaticConfig.CollectStatsEnabled) + // { + // if (Array.IndexOf(RecordCategories.All, key) >= 0) + // { + // foreach (Record r in recordCache) + // { + // if (r.Category.Equals(key)) + // { + // bool stored = false; + // foreach (RecordValue v in r.Values) + // { + // if (v.Key.Equals(playername)) + // { + // v.Value += value; + // stored = true; + // } + // } + // if (!stored) + // { + // r.Values.Add(new RecordValue() + // { + // Key = playername, + // Value = value + // }); + // } + // } + // } + // // After adding new data, flush data to disk. + // FlushCache().ContinueWith( + // t => Plugin.StaticLogger.LogWarning(t.Exception), + // TaskContinuationOptions.OnlyOnFaulted); + // } + // else + // { + // Plugin.StaticLogger.LogWarning($"Unable to store record of {key} for player {playername} - not considered a valid category."); + // } + // } + // } + /// + /// Get the value stored under at . + /// + /// The RecordCategories category the value is stored under + /// The name of the player + /// This will return 0 if there is no record found for that player. It will return -1 if the category is invalid. + // public int Retrieve(string key, string playername) + // { + // if (!Plugin.StaticConfig.CollectStatsEnabled) + // { + // return -1; + // } + // if (Array.IndexOf(RecordCategories.All, key) >= 0) + // { + // foreach (Record r in recordCache) + // { + // if (r.Category.Equals(key)) + // { + // foreach (RecordValue v in r.Values) + // { + // if (v.Key.Equals(playername)) + // { + // return v.Value; + // } + // } + // } + // } + // } + // Plugin.StaticLogger.LogWarning($"No stored record for player {playername} under {key}, returning default of 0."); + // return 0; + // } /// /// Retrieve all stored values under . diff --git a/src/Records/Classes/SimpleStat.cs b/src/Records/Classes/SimpleStat.cs new file mode 100644 index 0000000..5193df2 --- /dev/null +++ b/src/Records/Classes/SimpleStat.cs @@ -0,0 +1,67 @@ + +using LiteDB; + +namespace DiscordConnector.Records +{ + public class Position + { + public float x { get; } + public float y { get; } + public float z { get; } + + public Position() + { + x = 0; + y = 0; + z = 0; + } + public Position(float _x, float _y, float _z) + { + x = _x; + y = _y; + z = _z; + } + + public override string ToString() + { + return $"({x},{y},{z})"; + } + } + public class SimpleStat + { + public ObjectId StatId { get; } + public string Name { get; } + public System.DateTime Date { get; } + public long SteamId { get; } + public Position Pos { get; } + + public SimpleStat(string name, long steamId) + { + StatId = ObjectId.NewObjectId(); + Name = name; + SteamId = steamId; + Date = System.DateTime.Now; + Pos = new Position(); + } + + public SimpleStat(string name, long steamId, float x, float y, float z) + { + StatId = ObjectId.NewObjectId(); + Name = name; + SteamId = steamId; + Date = System.DateTime.Now; + Pos = new Position(x, y, z); + } + + [BsonCtor] + public SimpleStat(ObjectId _id, string name, System.DateTime date, long steamId, Position pos) + { + StatId = _id; + Name = name; + Date = date; + SteamId = steamId; + Pos = pos; + } + } + +} diff --git a/src/Records/Database.cs b/src/Records/Database.cs new file mode 100644 index 0000000..10fbcff --- /dev/null +++ b/src/Records/Database.cs @@ -0,0 +1,154 @@ + +using LiteDB; +using UnityEngine; + +namespace DiscordConnector.Records +{ + internal class Database + { + private const string DB_NAME = "records.db"; + private static string DbPath; + private LiteDatabase db; + private ILiteCollection DeathCollection; + private ILiteCollection JoinCollection; + private ILiteCollection LeaveCollection; + private ILiteCollection ShoutCollection; + private ILiteCollection PingCollection; + + public Database(string rootStorePath) + { + DbPath = System.IO.Path.Combine(rootStorePath, DB_NAME); + Initialize(); + } + + public void Initialize() + { + db = new LiteDatabase(DbPath); + Plugin.StaticLogger.LogDebug($"LiteDB Connection Established to {DbPath}"); + DeathCollection = db.GetCollection("deaths"); + JoinCollection = db.GetCollection("joins"); + LeaveCollection = db.GetCollection("leaves"); + ShoutCollection = db.GetCollection("shouts"); + PingCollection = db.GetCollection("pings"); + } + + public void Dispose() + { + Plugin.StaticLogger.LogDebug("Closing LiteDB connection"); + db.Dispose(); + } + + public void InsertDeathRecord(string playerName, long steamId, Vector3 pos) + { + InsertSimpleStatRecord(DeathCollection, playerName, steamId, pos); + } + public void InsertJoinRecord(string playerName, long steamId, Vector3 pos) + { + InsertSimpleStatRecord(JoinCollection, playerName, steamId, pos); + } + public void InsertLeaveRecord(string playerName, long steamId, Vector3 pos) + { + InsertSimpleStatRecord(LeaveCollection, playerName, steamId, pos); + } + public void InsertShoutRecord(string playerName, long steamId, Vector3 pos) + { + InsertSimpleStatRecord(ShoutCollection, playerName, steamId, pos); + } + public void InsertPingRecord(string playerName, long steamId, Vector3 pos) + { + InsertSimpleStatRecord(PingCollection, playerName, steamId, pos); + } + public void InsertDeathRecord(string playerName, long steamId) + { + InsertSimpleStatRecord(DeathCollection, playerName, steamId); + } + public void InsertJoinRecord(string playerName, long steamId) + { + InsertSimpleStatRecord(JoinCollection, playerName, steamId); + } + public void InsertLeaveRecord(string playerName, long steamId) + { + InsertSimpleStatRecord(LeaveCollection, playerName, steamId); + } + public void InsertShoutRecord(string playerName, long steamId) + { + InsertSimpleStatRecord(ShoutCollection, playerName, steamId); + } + public void InsertPingRecord(string playerName, long steamId) + { + InsertSimpleStatRecord(PingCollection, playerName, steamId); + } + + public int GetNumberDeaths(string playerName) + { + return CountOfRecordsByName(DeathCollection, playerName); + } + public int GetNumberDeaths(long steamId) + { + return CountOfRecordsBySteamId(DeathCollection, steamId); + } + public int GetNumberJoins(string playerName) + { + return CountOfRecordsByName(JoinCollection, playerName); + } + public int GetNumberJoins(long steamId) + { + return CountOfRecordsBySteamId(JoinCollection, steamId); + } + public int GetNumberLeaves(string playerName) + { + return CountOfRecordsByName(LeaveCollection, playerName); + } + public int GetNumberLeaves(long steamId) + { + return CountOfRecordsBySteamId(LeaveCollection, steamId); + } + public int GetNumberShouts(string playerName) + { + return CountOfRecordsByName(ShoutCollection, playerName); + } + public int GetNumberShouts(long steamId) + { + return CountOfRecordsBySteamId(ShoutCollection, steamId); + } + public int GetNumberPings(string playerName) + { + return CountOfRecordsByName(PingCollection, playerName); + } + public int GetNumberPings(long steamId) + { + return CountOfRecordsBySteamId(PingCollection, steamId); + } + + private void InsertSimpleStatRecord(ILiteCollection collection, string playerName, long steamId, Vector3 pos) + { + var newRecord = new SimpleStat( + playerName, + steamId, + pos.x, pos.y, pos.z + ); + collection.Insert(newRecord); + + collection.EnsureIndex(x => x.Name); + collection.EnsureIndex(x => x.SteamId); + } + private void InsertSimpleStatRecord(ILiteCollection collection, string playerName, long steamId) + { + InsertSimpleStatRecord(collection, playerName, steamId, Vector3.zero); + } + + private int CountOfRecordsByName(ILiteCollection collection, string playerName) + { + return DeathCollection.Query() + .Where(x => x.Name.Equals(playerName)) + .Count(); + } + + private int CountOfRecordsBySteamId(ILiteCollection collection, long steamId) + { + return DeathCollection.Query() + .Where(x => x.SteamId == steamId) + .Count(); + } + } +} From 6063a94fef669b1a201bcff3adafbbc32f3ba1ae Mon Sep 17 00:00:00 2001 From: Nicholas Westerhausen <2317381+nwesterhausen@users.noreply.github.com> Date: Tue, 26 Oct 2021 17:44:20 -0400 Subject: [PATCH 02/15] grab steam id from steam socket (it's ulong) --- src/Patches/ChatPatches.cs | 8 ++++--- src/Patches/ZNetPatches.cs | 8 ++++--- src/Records.cs | 6 +++--- src/Records/Classes/SimpleStat.cs | 8 +++---- src/Records/Database.cs | 36 +++++++++++++++---------------- 5 files changed, 35 insertions(+), 31 deletions(-) diff --git a/src/Patches/ChatPatches.cs b/src/Patches/ChatPatches.cs index 1860ccb..fa8a0c5 100644 --- a/src/Patches/ChatPatches.cs +++ b/src/Patches/ChatPatches.cs @@ -16,6 +16,8 @@ private static void Prefix(ref GameObject go, ref long senderID, ref Vector3 pos Plugin.StaticLogger.LogInfo($"Ignored shout from user on muted list. User: {user} Shout: {text}. Index {Plugin.StaticConfig.MutedPlayers.IndexOf(user)}"); return; } + + ulong peerSteamID = ((ZSteamSocket)ZNet.instance.GetPeerByPlayerName(user).m_socket).GetPeerID().m_SteamID; // Get the SteamID from peer. switch (type) { case Talker.Type.Ping: @@ -27,7 +29,7 @@ private static void Prefix(ref GameObject go, ref long senderID, ref Vector3 pos } if (Plugin.StaticConfig.StatsPingEnabled) { - Plugin.StaticDatabase.InsertPingRecord(user, senderID, pos); + Plugin.StaticDatabase.InsertPingRecord(user, peerSteamID, pos); } if (Plugin.StaticConfig.ChatPingEnabled) { @@ -64,7 +66,7 @@ private static void Prefix(ref GameObject go, ref long senderID, ref Vector3 pos } if (Plugin.StaticConfig.StatsJoinEnabled) { - Plugin.StaticDatabase.InsertJoinRecord(user, senderID, pos); + Plugin.StaticDatabase.InsertJoinRecord(user, peerSteamID, pos); } if (Plugin.StaticConfig.PlayerJoinMessageEnabled) { @@ -99,7 +101,7 @@ private static void Prefix(ref GameObject go, ref long senderID, ref Vector3 pos } if (Plugin.StaticConfig.StatsShoutEnabled) { - Plugin.StaticDatabase.InsertShoutRecord(user, senderID, pos); + Plugin.StaticDatabase.InsertShoutRecord(user, peerSteamID, pos); } if (Plugin.StaticConfig.ChatShoutEnabled) { diff --git a/src/Patches/ZNetPatches.cs b/src/Patches/ZNetPatches.cs index 133d301..6299b03 100644 --- a/src/Patches/ZNetPatches.cs +++ b/src/Patches/ZNetPatches.cs @@ -50,6 +50,7 @@ private static void Postfix(ZRpc rpc, ZDOID characterID) { return; } + ulong peerSteamID = ((ZSteamSocket)peer.m_socket).GetPeerID().m_SteamID; // Get the SteamID from peer. if (joinedPlayers.IndexOf(peer.m_uid) >= 0) { // Seems that player is dead if character ZDOID id is 0 @@ -104,7 +105,7 @@ private static void Postfix(ZRpc rpc, ZDOID characterID) if (Plugin.StaticConfig.StatsDeathEnabled) { - Plugin.StaticDatabase.InsertDeathRecord(peer.m_playerName, peer.m_uid, peer.m_refPos); + Plugin.StaticDatabase.InsertDeathRecord(peer.m_playerName, peerSteamID, peer.m_refPos); } } else @@ -156,7 +157,7 @@ private static void Postfix(ZRpc rpc, ZDOID characterID) if (Plugin.StaticConfig.StatsJoinEnabled) { - Plugin.StaticDatabase.InsertJoinRecord(peer.m_playerName, peer.m_uid, peer.m_refPos); + Plugin.StaticDatabase.InsertJoinRecord(peer.m_playerName, peerSteamID, peer.m_refPos); } } } @@ -215,7 +216,8 @@ private static void Prefix(ZRpc rpc) if (Plugin.StaticConfig.StatsLeaveEnabled) { - Plugin.StaticDatabase.InsertLeaveRecord(peer.m_playerName, peer.m_uid, peer.m_refPos); + ulong peerSteamID = ((ZSteamSocket)peer.m_socket).GetPeerID().m_SteamID; // Get the SteamID from peer. + Plugin.StaticDatabase.InsertLeaveRecord(peer.m_playerName, peerSteamID, peer.m_refPos); } } } diff --git a/src/Records.cs b/src/Records.cs index aafc4f7..e6fde04 100644 --- a/src/Records.cs +++ b/src/Records.cs @@ -71,7 +71,7 @@ public RecordsOld(string basePath, string fileName = null) } } - public void Store(string key, string playername, long steamId, Vector3 pos) + public void Store(string key, string playername, ulong steamId, Vector3 pos) { switch (key) { @@ -95,7 +95,7 @@ public void Store(string key, string playername, long steamId, Vector3 pos) break; } } - public void Store(string key, string playername, long steamId) + public void Store(string key, string playername, ulong steamId) { Store(key, playername, steamId, Vector3.zero); } @@ -123,7 +123,7 @@ public int Retrieve(string key, string playername) return -2; } } - public int Retrieve(string key, long steamId) + public int Retrieve(string key, ulong steamId) { if (!Plugin.StaticConfig.CollectStatsEnabled) { diff --git a/src/Records/Classes/SimpleStat.cs b/src/Records/Classes/SimpleStat.cs index 5193df2..5a081a1 100644 --- a/src/Records/Classes/SimpleStat.cs +++ b/src/Records/Classes/SimpleStat.cs @@ -32,10 +32,10 @@ public class SimpleStat public ObjectId StatId { get; } public string Name { get; } public System.DateTime Date { get; } - public long SteamId { get; } + public ulong SteamId { get; } public Position Pos { get; } - public SimpleStat(string name, long steamId) + public SimpleStat(string name, ulong steamId) { StatId = ObjectId.NewObjectId(); Name = name; @@ -44,7 +44,7 @@ public SimpleStat(string name, long steamId) Pos = new Position(); } - public SimpleStat(string name, long steamId, float x, float y, float z) + public SimpleStat(string name, ulong steamId, float x, float y, float z) { StatId = ObjectId.NewObjectId(); Name = name; @@ -54,7 +54,7 @@ public SimpleStat(string name, long steamId, float x, float y, float z) } [BsonCtor] - public SimpleStat(ObjectId _id, string name, System.DateTime date, long steamId, Position pos) + public SimpleStat(ObjectId _id, string name, System.DateTime date, ulong steamId, Position pos) { StatId = _id; Name = name; diff --git a/src/Records/Database.cs b/src/Records/Database.cs index 10fbcff..d292ed6 100644 --- a/src/Records/Database.cs +++ b/src/Records/Database.cs @@ -38,43 +38,43 @@ public void Dispose() db.Dispose(); } - public void InsertDeathRecord(string playerName, long steamId, Vector3 pos) + public void InsertDeathRecord(string playerName, ulong steamId, Vector3 pos) { InsertSimpleStatRecord(DeathCollection, playerName, steamId, pos); } - public void InsertJoinRecord(string playerName, long steamId, Vector3 pos) + public void InsertJoinRecord(string playerName, ulong steamId, Vector3 pos) { InsertSimpleStatRecord(JoinCollection, playerName, steamId, pos); } - public void InsertLeaveRecord(string playerName, long steamId, Vector3 pos) + public void InsertLeaveRecord(string playerName, ulong steamId, Vector3 pos) { InsertSimpleStatRecord(LeaveCollection, playerName, steamId, pos); } - public void InsertShoutRecord(string playerName, long steamId, Vector3 pos) + public void InsertShoutRecord(string playerName, ulong steamId, Vector3 pos) { InsertSimpleStatRecord(ShoutCollection, playerName, steamId, pos); } - public void InsertPingRecord(string playerName, long steamId, Vector3 pos) + public void InsertPingRecord(string playerName, ulong steamId, Vector3 pos) { InsertSimpleStatRecord(PingCollection, playerName, steamId, pos); } - public void InsertDeathRecord(string playerName, long steamId) + public void InsertDeathRecord(string playerName, ulong steamId) { InsertSimpleStatRecord(DeathCollection, playerName, steamId); } - public void InsertJoinRecord(string playerName, long steamId) + public void InsertJoinRecord(string playerName, ulong steamId) { InsertSimpleStatRecord(JoinCollection, playerName, steamId); } - public void InsertLeaveRecord(string playerName, long steamId) + public void InsertLeaveRecord(string playerName, ulong steamId) { InsertSimpleStatRecord(LeaveCollection, playerName, steamId); } - public void InsertShoutRecord(string playerName, long steamId) + public void InsertShoutRecord(string playerName, ulong steamId) { InsertSimpleStatRecord(ShoutCollection, playerName, steamId); } - public void InsertPingRecord(string playerName, long steamId) + public void InsertPingRecord(string playerName, ulong steamId) { InsertSimpleStatRecord(PingCollection, playerName, steamId); } @@ -83,7 +83,7 @@ public int GetNumberDeaths(string playerName) { return CountOfRecordsByName(DeathCollection, playerName); } - public int GetNumberDeaths(long steamId) + public int GetNumberDeaths(ulong steamId) { return CountOfRecordsBySteamId(DeathCollection, steamId); } @@ -91,7 +91,7 @@ public int GetNumberJoins(string playerName) { return CountOfRecordsByName(JoinCollection, playerName); } - public int GetNumberJoins(long steamId) + public int GetNumberJoins(ulong steamId) { return CountOfRecordsBySteamId(JoinCollection, steamId); } @@ -99,7 +99,7 @@ public int GetNumberLeaves(string playerName) { return CountOfRecordsByName(LeaveCollection, playerName); } - public int GetNumberLeaves(long steamId) + public int GetNumberLeaves(ulong steamId) { return CountOfRecordsBySteamId(LeaveCollection, steamId); } @@ -107,7 +107,7 @@ public int GetNumberShouts(string playerName) { return CountOfRecordsByName(ShoutCollection, playerName); } - public int GetNumberShouts(long steamId) + public int GetNumberShouts(ulong steamId) { return CountOfRecordsBySteamId(ShoutCollection, steamId); } @@ -115,12 +115,12 @@ public int GetNumberPings(string playerName) { return CountOfRecordsByName(PingCollection, playerName); } - public int GetNumberPings(long steamId) + public int GetNumberPings(ulong steamId) { return CountOfRecordsBySteamId(PingCollection, steamId); } - private void InsertSimpleStatRecord(ILiteCollection collection, string playerName, long steamId, Vector3 pos) + private void InsertSimpleStatRecord(ILiteCollection collection, string playerName, ulong steamId, Vector3 pos) { var newRecord = new SimpleStat( playerName, @@ -132,7 +132,7 @@ private void InsertSimpleStatRecord(ILiteCollection collection, stri collection.EnsureIndex(x => x.Name); collection.EnsureIndex(x => x.SteamId); } - private void InsertSimpleStatRecord(ILiteCollection collection, string playerName, long steamId) + private void InsertSimpleStatRecord(ILiteCollection collection, string playerName, ulong steamId) { InsertSimpleStatRecord(collection, playerName, steamId, Vector3.zero); } @@ -144,7 +144,7 @@ private int CountOfRecordsByName(ILiteCollection collection, string .Count(); } - private int CountOfRecordsBySteamId(ILiteCollection collection, long steamId) + private int CountOfRecordsBySteamId(ILiteCollection collection, ulong steamId) { return DeathCollection.Query() .Where(x => x.SteamId == steamId) From 183224783e6df054ac0905319397c2aa08e46c54 Mon Sep 17 00:00:00 2001 From: Nicholas Westerhausen <2317381+nwesterhausen@users.noreply.github.com> Date: Tue, 26 Oct 2021 20:06:03 -0400 Subject: [PATCH 03/15] move storage and retrieval for patches into database --- src/Patches/ChatPatches.cs | 12 +- src/Patches/ZNetPatches.cs | 12 +- src/Records.cs | 75 --------- src/Records/Classes/CountResult.cs | 19 +++ src/Records/Database.cs | 237 +++++++++++++++++++---------- src/Records/RecordsHelper.cs | 27 ++++ 6 files changed, 213 insertions(+), 169 deletions(-) create mode 100644 src/Records/Classes/CountResult.cs create mode 100644 src/Records/RecordsHelper.cs diff --git a/src/Patches/ChatPatches.cs b/src/Patches/ChatPatches.cs index fa8a0c5..b5688b2 100644 --- a/src/Patches/ChatPatches.cs +++ b/src/Patches/ChatPatches.cs @@ -21,7 +21,7 @@ private static void Prefix(ref GameObject go, ref long senderID, ref Vector3 pos switch (type) { case Talker.Type.Ping: - if (Plugin.StaticConfig.AnnouncePlayerFirstPingEnabled && Plugin.StaticRecords.Retrieve(RecordCategories.Ping, user) == 0) + if (Plugin.StaticConfig.AnnouncePlayerFirstPingEnabled && Plugin.StaticDatabase.CountOfRecordsByName(Records.Categories.Ping, user) == 0) { DiscordApi.SendMessage( MessageTransformer.FormatPlayerMessage(Plugin.StaticConfig.PlayerFirstPingMessage, user) @@ -29,7 +29,7 @@ private static void Prefix(ref GameObject go, ref long senderID, ref Vector3 pos } if (Plugin.StaticConfig.StatsPingEnabled) { - Plugin.StaticDatabase.InsertPingRecord(user, peerSteamID, pos); + Plugin.StaticDatabase.InsertSimpleStatRecord(Records.Categories.Ping, user, peerSteamID, pos); } if (Plugin.StaticConfig.ChatPingEnabled) { @@ -58,7 +58,7 @@ private static void Prefix(ref GameObject go, ref long senderID, ref Vector3 pos { if (!Plugin.IsHeadless()) { - if (Plugin.StaticConfig.AnnouncePlayerFirstJoinEnabled && Plugin.StaticRecords.Retrieve(RecordCategories.Join, user) == 0) + if (Plugin.StaticConfig.AnnouncePlayerFirstJoinEnabled && Plugin.StaticDatabase.CountOfRecordsByName(Records.Categories.Join, user) == 0) { DiscordApi.SendMessage( MessageTransformer.FormatPlayerMessage(Plugin.StaticConfig.PlayerFirstJoinMessage, user) @@ -66,7 +66,7 @@ private static void Prefix(ref GameObject go, ref long senderID, ref Vector3 pos } if (Plugin.StaticConfig.StatsJoinEnabled) { - Plugin.StaticDatabase.InsertJoinRecord(user, peerSteamID, pos); + Plugin.StaticDatabase.InsertSimpleStatRecord(Records.Categories.Join, user, peerSteamID, pos); } if (Plugin.StaticConfig.PlayerJoinMessageEnabled) { @@ -93,7 +93,7 @@ private static void Prefix(ref GameObject go, ref long senderID, ref Vector3 pos } else { - if (Plugin.StaticConfig.AnnouncePlayerFirstShoutEnabled && Plugin.StaticRecords.Retrieve(RecordCategories.Shout, user) == 0) + if (Plugin.StaticConfig.AnnouncePlayerFirstShoutEnabled && Plugin.StaticDatabase.CountOfRecordsByName(Records.Categories.Shout, user) == 0) { DiscordApi.SendMessage( MessageTransformer.FormatPlayerMessage(Plugin.StaticConfig.PlayerFirstShoutMessage, user, text) @@ -101,7 +101,7 @@ private static void Prefix(ref GameObject go, ref long senderID, ref Vector3 pos } if (Plugin.StaticConfig.StatsShoutEnabled) { - Plugin.StaticDatabase.InsertShoutRecord(user, peerSteamID, pos); + Plugin.StaticDatabase.InsertSimpleStatRecord(Records.Categories.Shout, user, peerSteamID, pos); } if (Plugin.StaticConfig.ChatShoutEnabled) { diff --git a/src/Patches/ZNetPatches.cs b/src/Patches/ZNetPatches.cs index 6299b03..6ff36e3 100644 --- a/src/Patches/ZNetPatches.cs +++ b/src/Patches/ZNetPatches.cs @@ -62,7 +62,7 @@ private static void Postfix(ZRpc rpc, ZDOID characterID) } if (Plugin.StaticConfig.PlayerDeathMessageEnabled) { - if (Plugin.StaticConfig.AnnouncePlayerFirstDeathEnabled && Plugin.StaticRecords.Retrieve(RecordCategories.Death, peer.m_playerName) == 0) + if (Plugin.StaticConfig.AnnouncePlayerFirstDeathEnabled && Plugin.StaticDatabase.CountOfRecordsByName(Records.Categories.Death, peer.m_playerName) == 0) { string firstDeathMessage = MessageTransformer.FormatPlayerMessage(Plugin.StaticConfig.PlayerFirstDeathMessage, peer.m_playerName); if (Plugin.StaticConfig.PlayerDeathPosEnabled) @@ -105,7 +105,7 @@ private static void Postfix(ZRpc rpc, ZDOID characterID) if (Plugin.StaticConfig.StatsDeathEnabled) { - Plugin.StaticDatabase.InsertDeathRecord(peer.m_playerName, peerSteamID, peer.m_refPos); + Plugin.StaticDatabase.InsertSimpleStatRecord(Records.Categories.Death, peer.m_playerName, peerSteamID, peer.m_refPos); } } else @@ -115,7 +115,7 @@ private static void Postfix(ZRpc rpc, ZDOID characterID) Plugin.StaticLogger.LogDebug($"Added player {peer.m_uid} ({peer.m_playerName}) to joined player list."); if (Plugin.StaticConfig.PlayerJoinMessageEnabled) { - if (Plugin.StaticConfig.AnnouncePlayerFirstJoinEnabled && Plugin.StaticRecords.Retrieve(RecordCategories.Join, peer.m_playerName) == 0) + if (Plugin.StaticConfig.AnnouncePlayerFirstJoinEnabled && Plugin.StaticDatabase.CountOfRecordsByName(Records.Categories.Join, peer.m_playerName) == 0) { string firstJoinMessage = MessageTransformer.FormatPlayerMessage(Plugin.StaticConfig.PlayerFirstJoinMessage, peer.m_playerName); if (Plugin.StaticConfig.PlayerJoinPosEnabled) @@ -157,7 +157,7 @@ private static void Postfix(ZRpc rpc, ZDOID characterID) if (Plugin.StaticConfig.StatsJoinEnabled) { - Plugin.StaticDatabase.InsertJoinRecord(peer.m_playerName, peerSteamID, peer.m_refPos); + Plugin.StaticDatabase.InsertSimpleStatRecord(Records.Categories.Join, peer.m_playerName, peerSteamID, peer.m_refPos); } } } @@ -173,7 +173,7 @@ private static void Prefix(ZRpc rpc) { if (Plugin.StaticConfig.PlayerLeaveMessageEnabled) { - if (Plugin.StaticConfig.AnnouncePlayerFirstLeaveEnabled && Plugin.StaticRecords.Retrieve(RecordCategories.Leave, peer.m_playerName) == 0) + if (Plugin.StaticConfig.AnnouncePlayerFirstLeaveEnabled && Plugin.StaticDatabase.CountOfRecordsByName(Records.Categories.Leave, peer.m_playerName) == 0) { string firstLeaveMessage = MessageTransformer.FormatPlayerMessage(Plugin.StaticConfig.PlayerFirstLeaveMessage, peer.m_playerName); if (Plugin.StaticConfig.PlayerLeavePosEnabled) @@ -217,7 +217,7 @@ private static void Prefix(ZRpc rpc) if (Plugin.StaticConfig.StatsLeaveEnabled) { ulong peerSteamID = ((ZSteamSocket)peer.m_socket).GetPeerID().m_SteamID; // Get the SteamID from peer. - Plugin.StaticDatabase.InsertLeaveRecord(peer.m_playerName, peerSteamID, peer.m_refPos); + Plugin.StaticDatabase.InsertSimpleStatRecord(Records.Categories.Leave, peer.m_playerName, peerSteamID, peer.m_refPos); } } } diff --git a/src/Records.cs b/src/Records.cs index e6fde04..272d225 100644 --- a/src/Records.cs +++ b/src/Records.cs @@ -71,81 +71,6 @@ public RecordsOld(string basePath, string fileName = null) } } - public void Store(string key, string playername, ulong steamId, Vector3 pos) - { - switch (key) - { - case RecordCategories.Death: - Plugin.StaticDatabase.InsertDeathRecord(playername, steamId, pos); - break; - case RecordCategories.Join: - Plugin.StaticDatabase.InsertJoinRecord(playername, steamId, pos); - break; - case RecordCategories.Leave: - Plugin.StaticDatabase.InsertLeaveRecord(playername, steamId, pos); - break; - case RecordCategories.Ping: - Plugin.StaticDatabase.InsertPingRecord(playername, steamId, pos); - break; - case RecordCategories.Shout: - Plugin.StaticDatabase.InsertShoutRecord(playername, steamId, pos); - break; - default: - Plugin.StaticLogger.LogDebug($"Unable to store record, invalid key '{key}'"); - break; - } - } - public void Store(string key, string playername, ulong steamId) - { - Store(key, playername, steamId, Vector3.zero); - } - - public int Retrieve(string key, string playername) - { - if (!Plugin.StaticConfig.CollectStatsEnabled) - { - return -1; - } - switch (key) - { - case RecordCategories.Death: - return Plugin.StaticDatabase.GetNumberDeaths(playername); - case RecordCategories.Join: - return Plugin.StaticDatabase.GetNumberJoins(playername); - case RecordCategories.Leave: - return Plugin.StaticDatabase.GetNumberLeaves(playername); - case RecordCategories.Ping: - return Plugin.StaticDatabase.GetNumberPings(playername); - case RecordCategories.Shout: - return Plugin.StaticDatabase.GetNumberShouts(playername); - default: - Plugin.StaticLogger.LogDebug($"Unable to retrieve record, invalid key '{key}'"); - return -2; - } - } - public int Retrieve(string key, ulong steamId) - { - if (!Plugin.StaticConfig.CollectStatsEnabled) - { - return -1; - } - switch (key) - { - case RecordCategories.Death: - return Plugin.StaticDatabase.GetNumberDeaths(steamId); - case RecordCategories.Join: - return Plugin.StaticDatabase.GetNumberJoins(steamId); - case RecordCategories.Leave: - return Plugin.StaticDatabase.GetNumberLeaves(steamId); - case RecordCategories.Ping: - return Plugin.StaticDatabase.GetNumberPings(steamId); - case RecordCategories.Shout: - return Plugin.StaticDatabase.GetNumberShouts(steamId); - default: - Plugin.StaticLogger.LogDebug($"Unable to retrieve record, invalid key '{key}'"); - return -2; - } - } /// /// Add to a record for under in the records database. diff --git a/src/Records/Classes/CountResult.cs b/src/Records/Classes/CountResult.cs new file mode 100644 index 0000000..015d451 --- /dev/null +++ b/src/Records/Classes/CountResult.cs @@ -0,0 +1,19 @@ + +using LiteDB; + +namespace DiscordConnector.Records +{ + public class CountResult + { + public string Name{get;} + public int Count{get;} + + [BsonCtor] + public CountResult(string name, int count) + { + + Name = name; + Count = count; + } + } +} diff --git a/src/Records/Database.cs b/src/Records/Database.cs index d292ed6..2f12cbf 100644 --- a/src/Records/Database.cs +++ b/src/Records/Database.cs @@ -1,4 +1,5 @@  +using System.Collections.Generic; using LiteDB; using UnityEngine; @@ -38,88 +39,6 @@ public void Dispose() db.Dispose(); } - public void InsertDeathRecord(string playerName, ulong steamId, Vector3 pos) - { - InsertSimpleStatRecord(DeathCollection, playerName, steamId, pos); - } - public void InsertJoinRecord(string playerName, ulong steamId, Vector3 pos) - { - InsertSimpleStatRecord(JoinCollection, playerName, steamId, pos); - } - public void InsertLeaveRecord(string playerName, ulong steamId, Vector3 pos) - { - InsertSimpleStatRecord(LeaveCollection, playerName, steamId, pos); - } - public void InsertShoutRecord(string playerName, ulong steamId, Vector3 pos) - { - InsertSimpleStatRecord(ShoutCollection, playerName, steamId, pos); - } - public void InsertPingRecord(string playerName, ulong steamId, Vector3 pos) - { - InsertSimpleStatRecord(PingCollection, playerName, steamId, pos); - } - public void InsertDeathRecord(string playerName, ulong steamId) - { - InsertSimpleStatRecord(DeathCollection, playerName, steamId); - } - public void InsertJoinRecord(string playerName, ulong steamId) - { - InsertSimpleStatRecord(JoinCollection, playerName, steamId); - } - public void InsertLeaveRecord(string playerName, ulong steamId) - { - InsertSimpleStatRecord(LeaveCollection, playerName, steamId); - } - public void InsertShoutRecord(string playerName, ulong steamId) - { - InsertSimpleStatRecord(ShoutCollection, playerName, steamId); - } - public void InsertPingRecord(string playerName, ulong steamId) - { - InsertSimpleStatRecord(PingCollection, playerName, steamId); - } - - public int GetNumberDeaths(string playerName) - { - return CountOfRecordsByName(DeathCollection, playerName); - } - public int GetNumberDeaths(ulong steamId) - { - return CountOfRecordsBySteamId(DeathCollection, steamId); - } - public int GetNumberJoins(string playerName) - { - return CountOfRecordsByName(JoinCollection, playerName); - } - public int GetNumberJoins(ulong steamId) - { - return CountOfRecordsBySteamId(JoinCollection, steamId); - } - public int GetNumberLeaves(string playerName) - { - return CountOfRecordsByName(LeaveCollection, playerName); - } - public int GetNumberLeaves(ulong steamId) - { - return CountOfRecordsBySteamId(LeaveCollection, steamId); - } - public int GetNumberShouts(string playerName) - { - return CountOfRecordsByName(ShoutCollection, playerName); - } - public int GetNumberShouts(ulong steamId) - { - return CountOfRecordsBySteamId(ShoutCollection, steamId); - } - public int GetNumberPings(string playerName) - { - return CountOfRecordsByName(PingCollection, playerName); - } - public int GetNumberPings(ulong steamId) - { - return CountOfRecordsBySteamId(PingCollection, steamId); - } - private void InsertSimpleStatRecord(ILiteCollection collection, string playerName, ulong steamId, Vector3 pos) { var newRecord = new SimpleStat( @@ -150,5 +69,159 @@ private int CountOfRecordsBySteamId(ILiteCollection collection, ulon .Where(x => x.SteamId == steamId) .Count(); } + + private List CountAllRecordsGroupBySteamId(ILiteCollection collection) + { + return ConvertBsonDocumentCountToDotNet( + collection.Query() + .GroupBy("SteamId") + .Select("{Name: SteamId, Count: COUNT(*)}") + .ToList() + ); + } + + private List RetrieveAllRecordsGroupByName(ILiteCollection collection) + { + return ConvertBsonDocumentCountToDotNet( + collection.Query() + .GroupBy("Name") + .Select("{Name: Name, Count: COUNT(*)}") + .ToList() + ); + } + + private List ConvertBsonDocumentCountToDotNet(List bsonDocuments) + { + List results = new List(); + foreach (BsonDocument doc in bsonDocuments) + { + if (doc.ContainsKey("Name") && doc.ContainsKey("Count")) + { + results.Add(new CountResult ( + doc["Name"].AsString, + doc["Count"].AsInt32 + )); + } + } + return results; + } + + public List RetrieveAllRecordsGroupByName(string key) + { + switch (key) + { + case Categories.Death: + return RetrieveAllRecordsGroupByName(DeathCollection); + case Categories.Join: + return RetrieveAllRecordsGroupByName(JoinCollection); + case Categories.Leave: + return RetrieveAllRecordsGroupByName(LeaveCollection); + case Categories.Ping: + return RetrieveAllRecordsGroupByName(PingCollection); + case Categories.Shout: + return RetrieveAllRecordsGroupByName(ShoutCollection); + default: + Plugin.StaticLogger.LogDebug($"RetrieveAllRecordsGroupByName, invalid key '{key}'"); + return new List(); + } + } + public List CountAllRecordsGroupBySteamId(string key) + { + switch (key) + { + case Categories.Death: + return CountAllRecordsGroupBySteamId(DeathCollection); + case Categories.Join: + return CountAllRecordsGroupBySteamId(JoinCollection); + case Categories.Leave: + return CountAllRecordsGroupBySteamId(LeaveCollection); + case Categories.Ping: + return CountAllRecordsGroupBySteamId(PingCollection); + case Categories.Shout: + return CountAllRecordsGroupBySteamId(ShoutCollection); + default: + Plugin.StaticLogger.LogDebug($"CountAllRecordsGroupBySteamId, invalid key '{key}'"); + return new List(); + } + } + + public int CountOfRecordsByName(string key, string playerName) + { + if (!Plugin.StaticConfig.CollectStatsEnabled) + { + return -1; + } + switch (key) + { + case Categories.Death: + return CountOfRecordsByName(DeathCollection, playerName); + case Categories.Join: + return CountOfRecordsByName(JoinCollection, playerName); + case Categories.Leave: + return CountOfRecordsByName(LeaveCollection, playerName); + case Categories.Ping: + return CountOfRecordsByName(PingCollection, playerName); + case Categories.Shout: + return CountOfRecordsByName(ShoutCollection, playerName); + default: + Plugin.StaticLogger.LogDebug($"CountOfRecordsBySteamId, invalid key '{key}'"); + return -2; + } + } + + public int CountOfRecordsBySteamId(string key, ulong steamId) + { + if (!Plugin.StaticConfig.CollectStatsEnabled) + { + return -1; + } + switch (key) + { + case Categories.Death: + return CountOfRecordsBySteamId(DeathCollection, steamId); + case Categories.Join: + return CountOfRecordsBySteamId(JoinCollection, steamId); + case Categories.Leave: + return CountOfRecordsBySteamId(LeaveCollection, steamId); + case Categories.Ping: + return CountOfRecordsBySteamId(PingCollection, steamId); + case Categories.Shout: + return CountOfRecordsBySteamId(ShoutCollection, steamId); + default: + Plugin.StaticLogger.LogDebug($"CountOfRecordsBySteamId, invalid key '{key}'"); + return -2; + } + } + + public void InsertSimpleStatRecord(string key, string playerName, ulong steamId, Vector3 pos) + { + + switch (key) + { + case Categories.Death: + InsertSimpleStatRecord(DeathCollection, playerName, steamId, pos); + break; + case Categories.Join: + InsertSimpleStatRecord(JoinCollection, playerName, steamId, pos); + break; + case Categories.Leave: + InsertSimpleStatRecord(LeaveCollection, playerName, steamId, pos); + break; + case Categories.Ping: + InsertSimpleStatRecord(PingCollection, playerName, steamId, pos); + break; + case Categories.Shout: + InsertSimpleStatRecord(ShoutCollection, playerName, steamId, pos); + break; + default: + Plugin.StaticLogger.LogDebug($"InsertSimpleStatRecord, invalid key '{key}'"); + break; + } + } + public void InsertSimpleStatRecord(string key, string playerName, ulong steamId) + { + InsertSimpleStatRecord(key, playerName, steamId, Vector3.zero); + } + } } diff --git a/src/Records/RecordsHelper.cs b/src/Records/RecordsHelper.cs new file mode 100644 index 0000000..8a621d2 --- /dev/null +++ b/src/Records/RecordsHelper.cs @@ -0,0 +1,27 @@ +using System; +using System.Collections.Generic; +using UnityEngine; + +namespace DiscordConnector.Records +{ + /// + /// These are categories used when keeping track of values in the record system. It is a simple system + /// that currently only supports storing string:integer pairings underneath one of these categories. + /// + public static class Categories + { + public const string Death = "death"; + public const string Join = "join"; + public const string Leave = "leave"; + public const string Ping = "ping"; + public const string Shout = "shout"; + + public readonly static string[] All = new string[] { + Death, + Join, + Leave, + Ping, + Shout + }; + } + } From d1a87824cc95667c9542041179480586b14d2a2a Mon Sep 17 00:00:00 2001 From: Nicholas Westerhausen <2317381+nwesterhausen@users.noreply.github.com> Date: Wed, 27 Oct 2021 16:33:12 -0400 Subject: [PATCH 04/15] convert leaderboards to using database --- src/Leaderboard/Leaderboard.cs | 21 +++++++++++- src/Leaderboard/OverallHighest.cs | 24 +++++++------- src/Leaderboard/OverallLowest.cs | 24 +++++++------- src/Leaderboard/TopPlayers.cs | 39 +++++------------------ src/Records.cs | 44 +++++++------------------- src/Records/Classes/CountResult.cs | 10 ++++++ src/Records/RecordsHelper.cs | 51 ++++++++++++++++++++++++++++++ 7 files changed, 125 insertions(+), 88 deletions(-) diff --git a/src/Leaderboard/Leaderboard.cs b/src/Leaderboard/Leaderboard.cs index d928538..c490086 100644 --- a/src/Leaderboard/Leaderboard.cs +++ b/src/Leaderboard/Leaderboard.cs @@ -1,4 +1,6 @@ -using System.Timers; +using System; +using System.Collections.Generic; +using System.Timers; namespace DiscordConnector { @@ -18,6 +20,23 @@ public Leaderboard() public Leaderboards.Base OverallHighest => overallHighest; public Leaderboards.Base OverallLowest => overallLowest; public Leaderboards.Base TopPlayers => topPlayers; + + + + /// + /// Takes a sorted list and returns a string listing each member on a line prepended with 1, 2, 3, etc. + /// + /// A pre-sorted list of CountResults. + /// String ready to send to discord listing each player and their value. + public static string RankedCountResultToString(List rankings) + { + string res = ""; + for (int i = 0; i < rankings.Count; i++) + { + res += $"{i + 1}: {rankings[i].Name}: {rankings[i].Count}{Environment.NewLine}"; + } + return res; + } } } diff --git a/src/Leaderboard/OverallHighest.cs b/src/Leaderboard/OverallHighest.cs index 3aeb75c..d349954 100644 --- a/src/Leaderboard/OverallHighest.cs +++ b/src/Leaderboard/OverallHighest.cs @@ -7,27 +7,27 @@ internal class OverallHighest : Base { public override void SendLeaderboard() { - var deathLeader = Plugin.StaticRecords.RetrieveHighest(RecordCategories.Death); - var joinLeader = Plugin.StaticRecords.RetrieveHighest(RecordCategories.Join); - var shoutLeader = Plugin.StaticRecords.RetrieveHighest(RecordCategories.Shout); - var pingLeader = Plugin.StaticRecords.RetrieveHighest(RecordCategories.Ping); + var deathLeader = Records.Helper.TopResultForCategory(Records.Categories.Death); + var joinLeader = Records.Helper.TopResultForCategory(Records.Categories.Join); + var shoutLeader = Records.Helper.TopResultForCategory(Records.Categories.Shout); + var pingLeader = Records.Helper.TopResultForCategory(Records.Categories.Ping); List> leaderFields = new List>(); - if (Plugin.StaticConfig.MostDeathLeaderboardEnabled && deathLeader.Item2 > 0) + if (Plugin.StaticConfig.MostDeathLeaderboardEnabled && deathLeader.Count > 0) { - leaderFields.Add(Tuple.Create("Most Deaths", $"{deathLeader.Item1} ({deathLeader.Item2})")); + leaderFields.Add(Tuple.Create("Most Deaths", $"{deathLeader.Name} ({deathLeader.Count})")); } - if (Plugin.StaticConfig.MostSessionLeaderboardEnabled && joinLeader.Item2 > 0) + if (Plugin.StaticConfig.MostSessionLeaderboardEnabled && joinLeader.Count > 0) { - leaderFields.Add(Tuple.Create("Most Sessions", $"{joinLeader.Item1} ({joinLeader.Item2})")); + leaderFields.Add(Tuple.Create("Most Sessions", $"{joinLeader.Name} ({joinLeader.Count})")); } - if (Plugin.StaticConfig.MostShoutLeaderboardEnabled && shoutLeader.Item2 > 0) + if (Plugin.StaticConfig.MostShoutLeaderboardEnabled && shoutLeader.Count > 0) { - leaderFields.Add(Tuple.Create("Most Shouts", $"{shoutLeader.Item1} ({shoutLeader.Item2})")); + leaderFields.Add(Tuple.Create("Most Shouts", $"{shoutLeader.Name} ({shoutLeader.Count})")); } - if (Plugin.StaticConfig.MostPingLeaderboardEnabled && pingLeader.Item2 > 0) + if (Plugin.StaticConfig.MostPingLeaderboardEnabled && pingLeader.Count > 0) { - leaderFields.Add(Tuple.Create("Most Pings", $"{pingLeader.Item1} ({pingLeader.Item2})")); + leaderFields.Add(Tuple.Create("Most Pings", $"{pingLeader.Name} ({pingLeader.Count})")); } if (leaderFields.Count > 0) { diff --git a/src/Leaderboard/OverallLowest.cs b/src/Leaderboard/OverallLowest.cs index 6ad46e1..ca67d28 100644 --- a/src/Leaderboard/OverallLowest.cs +++ b/src/Leaderboard/OverallLowest.cs @@ -7,27 +7,27 @@ internal class OverallLowest : Base { public override void SendLeaderboard() { - var deathLeader = Plugin.StaticRecords.RetrieveLowest(RecordCategories.Death); - var joinLeader = Plugin.StaticRecords.RetrieveLowest(RecordCategories.Join); - var shoutLeader = Plugin.StaticRecords.RetrieveLowest(RecordCategories.Shout); - var pingLeader = Plugin.StaticRecords.RetrieveLowest(RecordCategories.Ping); + var deathLeader = Records.Helper.BottomResultForCategory(Records.Categories.Death); + var joinLeader = Records.Helper.BottomResultForCategory(Records.Categories.Join); + var shoutLeader = Records.Helper.BottomResultForCategory(Records.Categories.Shout); + var pingLeader = Records.Helper.BottomResultForCategory(Records.Categories.Ping); List> leaderFields = new List>(); - if (Plugin.StaticConfig.LeastDeathLeaderboardEnabled && deathLeader.Item2 > 0) + if (Plugin.StaticConfig.LeastDeathLeaderboardEnabled && deathLeader.Count > 0) { - leaderFields.Add(Tuple.Create("Least Deaths", $"{deathLeader.Item1} ({deathLeader.Item2})")); + leaderFields.Add(Tuple.Create("Least Deaths", $"{deathLeader.Name} ({deathLeader.Count})")); } - if (Plugin.StaticConfig.LeastSessionLeaderboardEnabled && joinLeader.Item2 > 0) + if (Plugin.StaticConfig.LeastSessionLeaderboardEnabled && joinLeader.Count > 0) { - leaderFields.Add(Tuple.Create("Least Sessions", $"{joinLeader.Item1} ({joinLeader.Item2})")); + leaderFields.Add(Tuple.Create("Least Sessions", $"{joinLeader.Name} ({joinLeader.Count})")); } - if (Plugin.StaticConfig.LeastShoutLeaderboardEnabled && shoutLeader.Item2 > 0) + if (Plugin.StaticConfig.LeastShoutLeaderboardEnabled && shoutLeader.Count > 0) { - leaderFields.Add(Tuple.Create("Least Shouts", $"{shoutLeader.Item1} ({shoutLeader.Item2})")); + leaderFields.Add(Tuple.Create("Least Shouts", $"{shoutLeader.Name} ({shoutLeader.Count})")); } - if (Plugin.StaticConfig.LeastPingLeaderboardEnabled && pingLeader.Item2 > 0) + if (Plugin.StaticConfig.LeastPingLeaderboardEnabled && pingLeader.Count > 0) { - leaderFields.Add(Tuple.Create("Least Pings", $"{pingLeader.Item1} ({pingLeader.Item2})")); + leaderFields.Add(Tuple.Create("Least Pings", $"{pingLeader.Name} ({pingLeader.Count})")); } if (leaderFields.Count > 0) { diff --git a/src/Leaderboard/TopPlayers.cs b/src/Leaderboard/TopPlayers.cs index 31d2d1a..1fa803a 100644 --- a/src/Leaderboard/TopPlayers.cs +++ b/src/Leaderboard/TopPlayers.cs @@ -7,31 +7,27 @@ internal class TopPlayers : Base { public override void SendLeaderboard() { - var deaths = Plugin.StaticRecords.RetrieveAll(RecordCategories.Death); - var sessions = Plugin.StaticRecords.RetrieveAll(RecordCategories.Join); - var shouts = Plugin.StaticRecords.RetrieveAll(RecordCategories.Shout); - var pings = Plugin.StaticRecords.RetrieveAll(RecordCategories.Ping); + var deaths = Records.Helper.TopNResultForCategory(Records.Categories.Death, Plugin.StaticConfig.IncludedNumberOfRankings); + var sessions = Records.Helper.TopNResultForCategory(Records.Categories.Join, Plugin.StaticConfig.IncludedNumberOfRankings); + var shouts = Records.Helper.TopNResultForCategory(Records.Categories.Shout, Plugin.StaticConfig.IncludedNumberOfRankings); + var pings = Records.Helper.TopNResultForCategory(Records.Categories.Ping, Plugin.StaticConfig.IncludedNumberOfRankings); List> leaderFields = new List>(); if (Plugin.StaticConfig.RankedDeathLeaderboardEnabled && deaths.Count > 0) { - deaths.Sort(Plugin.StaticRecords.HighToLowSort); - leaderFields.Add(Tuple.Create("Top Deaths", TopPlayersFormater(deaths.ToArray()))); + leaderFields.Add(Tuple.Create("Top Deaths", Leaderboard.RankedCountResultToString(deaths))); } if (Plugin.StaticConfig.RankedSessionLeaderboardEnabled && sessions.Count > 0) { - sessions.Sort(Plugin.StaticRecords.HighToLowSort); - leaderFields.Add(Tuple.Create("Top Sessions", TopPlayersFormater(sessions.ToArray()))); + leaderFields.Add(Tuple.Create("Top Sessions", Leaderboard.RankedCountResultToString(sessions))); } if (Plugin.StaticConfig.RankedShoutLeaderboardEnabled && shouts.Count > 0) { - shouts.Sort(Plugin.StaticRecords.HighToLowSort); - leaderFields.Add(Tuple.Create("Top Shouts", TopPlayersFormater(shouts.ToArray()))); + leaderFields.Add(Tuple.Create("Top Shouts", Leaderboard.RankedCountResultToString(shouts))); } if (Plugin.StaticConfig.RankedPingLeaderboardEnabled && pings.Count > 0) { - pings.Sort(Plugin.StaticRecords.HighToLowSort); - leaderFields.Add(Tuple.Create("Top Pings", TopPlayersFormater(pings.ToArray()))); + leaderFields.Add(Tuple.Create("Top Pings", Leaderboard.RankedCountResultToString(pings))); } if (leaderFields.Count > 0) { @@ -42,24 +38,5 @@ public override void SendLeaderboard() Plugin.StaticLogger.LogInfo("Not sending a leaderboard because theirs either no leaders, or nothing allowed."); } } - - /// - /// Takes a sorted array and returns a string combining the top n results (n as defined in config). - /// - /// A pre-sorted array of (playername, value) Tuples. - /// String ready to send to discord listing each player and their value. - private string TopPlayersFormater(Tuple[] sortedTopPlayers) - { - string result = ""; - for (int i = 0; i < Plugin.StaticConfig.IncludedNumberOfRankings; i++) - { - if (i < sortedTopPlayers.Length) - { - Tuple player = sortedTopPlayers[i]; - result += $"{i + 1}: {player.Item1}: {player.Item2}{Environment.NewLine}"; - } - } - return result; - } } } diff --git a/src/Records.cs b/src/Records.cs index 272d225..1a5e11f 100644 --- a/src/Records.cs +++ b/src/Records.cs @@ -7,26 +7,6 @@ namespace DiscordConnector { - /// - /// These are categories used when keeping track of values in the record system. It is a simple system - /// that currently only supports storing string:integer pairings underneath one of these categories. - /// - public static class RecordCategories - { - public const string Death = "death"; - public const string Join = "join"; - public const string Leave = "leave"; - public const string Ping = "ping"; - public const string Shout = "shout"; - - public static string[] All = new string[] { - Death, - Join, - Leave, - Ping, - Shout - }; - } /// /// The individual key:value pairings used in the record system. It supports only string:int pairings. /// @@ -74,16 +54,16 @@ public RecordsOld(string basePath, string fileName = null) /// /// Add to a record for under in the records database. - /// This will not save the record if the is not one defined in RecordCategories. + /// This will not save the record if the is not one defined in Records.Categories. /// - /// RecordCategories category to store the value under + /// Records.Categories category to store the value under /// The player's name. /// How much to increase current stored value by. // public void Store(string key, string playername, int value) // { // if (Plugin.StaticConfig.CollectStatsEnabled) // { - // if (Array.IndexOf(RecordCategories.All, key) >= 0) + // if (Array.IndexOf(Records.Categories.All, key) >= 0) // { // foreach (Record r in recordCache) // { @@ -122,7 +102,7 @@ public RecordsOld(string basePath, string fileName = null) /// /// Get the value stored under at . /// - /// The RecordCategories category the value is stored under + /// The Records.Categories category the value is stored under /// The name of the player /// This will return 0 if there is no record found for that player. It will return -1 if the category is invalid. // public int Retrieve(string key, string playername) @@ -131,7 +111,7 @@ public RecordsOld(string basePath, string fileName = null) // { // return -1; // } - // if (Array.IndexOf(RecordCategories.All, key) >= 0) + // if (Array.IndexOf(Records.Categories.All, key) >= 0) // { // foreach (Record r in recordCache) // { @@ -154,7 +134,7 @@ public RecordsOld(string basePath, string fileName = null) /// /// Retrieve all stored values under . /// - /// RecordCategories category to retrieve stored values from + /// Records.Categories category to retrieve stored values from /// A list of (playername, value) tuples. The list will have length 0 if there are no stored records. public List> RetrieveAll(string key) { @@ -164,7 +144,7 @@ public List> RetrieveAll(string key) return results; } - if (Array.IndexOf(RecordCategories.All, key) >= 0) + if (Array.IndexOf(Records.Categories.All, key) >= 0) { foreach (Record r in recordCache) { @@ -186,7 +166,7 @@ public List> RetrieveAll(string key) /// /// Retrieve the highest stored value under . /// - /// RecordCategories category to retrieve stored values from + /// Records.Categories category to retrieve stored values from /// A single (playername, value) tuple. public Tuple RetrieveHighest(string key) { @@ -195,7 +175,7 @@ public Tuple RetrieveHighest(string key) return Tuple.Create("not allowed", -1); } - if (Array.IndexOf(RecordCategories.All, key) >= 0) + if (Array.IndexOf(Records.Categories.All, key) >= 0) { string player = "no result"; int records = -1; @@ -224,7 +204,7 @@ public Tuple RetrieveHighest(string key) /// /// Retrieve the lowest stored value under . /// - /// RecordCategories category to retrieve stored values from + /// Records.Categories category to retrieve stored values from /// A single (playername, value) tuple. public Tuple RetrieveLowest(string key) { @@ -233,7 +213,7 @@ public Tuple RetrieveLowest(string key) return Tuple.Create("not allowed", -1); } - if (Array.IndexOf(RecordCategories.All, key) >= 0) + if (Array.IndexOf(Records.Categories.All, key) >= 0) { string player = "no result"; int records = int.MaxValue; @@ -322,7 +302,7 @@ private void PopulateCache() private void InitializeEmptyCache() { recordCache = new List(); - foreach (string category in RecordCategories.All) + foreach (string category in Records.Categories.All) { recordCache.Add(new Record { diff --git a/src/Records/Classes/CountResult.cs b/src/Records/Classes/CountResult.cs index 015d451..cb1c10b 100644 --- a/src/Records/Classes/CountResult.cs +++ b/src/Records/Classes/CountResult.cs @@ -15,5 +15,15 @@ public CountResult(string name, int count) Name = name; Count = count; } + + public static int CompareByCount(CountResult cr1, CountResult cr2) + { + return cr1.Count.CompareTo(cr2.Count); + } + + public static int CompareByName(CountResult cr1, CountResult cr2) + { + return cr1.Name.CompareTo(cr2.Name); + } } } diff --git a/src/Records/RecordsHelper.cs b/src/Records/RecordsHelper.cs index 8a621d2..7b09388 100644 --- a/src/Records/RecordsHelper.cs +++ b/src/Records/RecordsHelper.cs @@ -24,4 +24,55 @@ public static class Categories Shout }; } + + public static class Helper + { + public static List TopNResultForCategory(string key, int n) + { + List queryResults = Plugin.StaticDatabase.RetrieveAllRecordsGroupByName(key); + if (queryResults.Count == 0) + { + return queryResults; + } + + queryResults.Sort(CountResult.CompareByCount); // sorts lowest to highest + queryResults.Reverse(); // Now high --> low + + if (queryResults.Count <= n) + { + return queryResults; + } + + return queryResults.GetRange(0, n); + } + + public static CountResult TopResultForCategory(string key) + { + return Helper.TopNResultForCategory(key, 1)[0]; + } + + public static List BottomNResultForCategory(string key, int n) + { + + List queryResults = Plugin.StaticDatabase.RetrieveAllRecordsGroupByName(key); + if (queryResults.Count == 0) + { + return queryResults; + } + + queryResults.Sort(CountResult.CompareByCount); // sorts lowest to highest + + if (queryResults.Count <= n) + { + return queryResults; + } + + return queryResults.GetRange(0, n); + } + + public static CountResult BottomResultForCategory(string key) + { + return Helper.BottomNResultForCategory(key, 1)[0]; + } + } } From ba26cf1f0842c764cc050a1814ef6ef1cb5727b0 Mon Sep 17 00:00:00 2001 From: Nicholas Westerhausen <2317381+nwesterhausen@users.noreply.github.com> Date: Wed, 27 Oct 2021 17:33:29 -0400 Subject: [PATCH 05/15] add bottom leaderboard --- src/Leaderboard/BottomPlayer.cs | 45 ++++++++++++++++++++++++++++++ src/Leaderboard/Leaderboard.cs | 4 +++ src/Leaderboard/TopPlayers.cs | 5 +++- src/Plugin.cs | 2 ++ src/Records/Classes/CountResult.cs | 38 ++++++++++++------------- src/Records/Database.cs | 16 +++++------ src/Records/RecordsHelper.cs | 22 +++++++++++---- 7 files changed, 99 insertions(+), 33 deletions(-) create mode 100644 src/Leaderboard/BottomPlayer.cs diff --git a/src/Leaderboard/BottomPlayer.cs b/src/Leaderboard/BottomPlayer.cs new file mode 100644 index 0000000..105ce26 --- /dev/null +++ b/src/Leaderboard/BottomPlayer.cs @@ -0,0 +1,45 @@ +using System; +using System.Collections.Generic; + +namespace DiscordConnector.Leaderboards +{ + internal class BottomPlayers : Base + { + public override void SendLeaderboard() + { + List> leaderFields = new List>(); + + var deaths = Records.Helper.BottomNResultForCategory(Records.Categories.Death, Plugin.StaticConfig.IncludedNumberOfRankings); + var sessions = Records.Helper.BottomNResultForCategory(Records.Categories.Join, Plugin.StaticConfig.IncludedNumberOfRankings); + var shouts = Records.Helper.BottomNResultForCategory(Records.Categories.Shout, Plugin.StaticConfig.IncludedNumberOfRankings); + var pings = Records.Helper.BottomNResultForCategory(Records.Categories.Ping, Plugin.StaticConfig.IncludedNumberOfRankings); + + + + if (Plugin.StaticConfig.RankedDeathLeaderboardEnabled && deaths.Count > 0) + { + leaderFields.Add(Tuple.Create("Bottom Deaths", Leaderboard.RankedCountResultToString(deaths))); + } + if (Plugin.StaticConfig.RankedSessionLeaderboardEnabled && sessions.Count > 0) + { + leaderFields.Add(Tuple.Create("Bottom Sessions", Leaderboard.RankedCountResultToString(sessions))); + } + if (Plugin.StaticConfig.RankedShoutLeaderboardEnabled && shouts.Count > 0) + { + leaderFields.Add(Tuple.Create("Bottom Shouts", Leaderboard.RankedCountResultToString(shouts))); + } + if (Plugin.StaticConfig.RankedPingLeaderboardEnabled && pings.Count > 0) + { + leaderFields.Add(Tuple.Create("Bottom Pings", Leaderboard.RankedCountResultToString(pings))); + } + if (leaderFields.Count > 0) + { + DiscordApi.SendMessageWithFields("Current Bottom player leaderboard:", leaderFields); + } + else + { + Plugin.StaticLogger.LogInfo("Not sending a leaderboard because theirs either no leaders, or nothing allowed."); + } + } + } +} diff --git a/src/Leaderboard/Leaderboard.cs b/src/Leaderboard/Leaderboard.cs index c490086..f2ac408 100644 --- a/src/Leaderboard/Leaderboard.cs +++ b/src/Leaderboard/Leaderboard.cs @@ -9,17 +9,20 @@ internal class Leaderboard private Leaderboards.Base overallHighest; private Leaderboards.Base overallLowest; private Leaderboards.Base topPlayers; + private Leaderboards.Base bottomPlayers; public Leaderboard() { overallHighest = new Leaderboards.OverallHighest(); overallLowest = new Leaderboards.OverallLowest(); topPlayers = new Leaderboards.TopPlayers(); + bottomPlayers = new Leaderboards.BottomPlayers(); } public Leaderboards.Base OverallHighest => overallHighest; public Leaderboards.Base OverallLowest => overallLowest; public Leaderboards.Base TopPlayers => topPlayers; + public Leaderboards.Base BottomPlayers => bottomPlayers; @@ -49,6 +52,7 @@ internal abstract class Base /// public void SendLeaderboardOnTimer(object sender, ElapsedEventArgs elapsedEventArgs) { + Plugin.StaticLogger.LogDebug($"Running the leaderboard send"); this.SendLeaderboard(); } diff --git a/src/Leaderboard/TopPlayers.cs b/src/Leaderboard/TopPlayers.cs index 1fa803a..88927a7 100644 --- a/src/Leaderboard/TopPlayers.cs +++ b/src/Leaderboard/TopPlayers.cs @@ -7,12 +7,15 @@ internal class TopPlayers : Base { public override void SendLeaderboard() { + List> leaderFields = new List>(); + var deaths = Records.Helper.TopNResultForCategory(Records.Categories.Death, Plugin.StaticConfig.IncludedNumberOfRankings); var sessions = Records.Helper.TopNResultForCategory(Records.Categories.Join, Plugin.StaticConfig.IncludedNumberOfRankings); var shouts = Records.Helper.TopNResultForCategory(Records.Categories.Shout, Plugin.StaticConfig.IncludedNumberOfRankings); var pings = Records.Helper.TopNResultForCategory(Records.Categories.Ping, Plugin.StaticConfig.IncludedNumberOfRankings); - List> leaderFields = new List>(); + + if (Plugin.StaticConfig.RankedDeathLeaderboardEnabled && deaths.Count > 0) { leaderFields.Add(Tuple.Create("Top Deaths", Leaderboard.RankedCountResultToString(deaths))); diff --git a/src/Plugin.cs b/src/Plugin.cs index 013c01a..d85698a 100644 --- a/src/Plugin.cs +++ b/src/Plugin.cs @@ -52,8 +52,10 @@ private void Awake() leaderboardTimer.Elapsed += StaticLeaderboards.OverallHighest.SendLeaderboardOnTimer; leaderboardTimer.Elapsed += StaticLeaderboards.OverallLowest.SendLeaderboardOnTimer; leaderboardTimer.Elapsed += StaticLeaderboards.TopPlayers.SendLeaderboardOnTimer; + leaderboardTimer.Elapsed += StaticLeaderboards.BottomPlayers.SendLeaderboardOnTimer; // Interval is learned from config file in minutes leaderboardTimer.Interval = 60 * 1000 * StaticConfig.StatsAnnouncementPeriod; + Plugin.StaticLogger.LogDebug($"Enabling leaderboard timers with interval {leaderboardTimer.Interval}ms"); leaderboardTimer.Start(); } diff --git a/src/Records/Classes/CountResult.cs b/src/Records/Classes/CountResult.cs index cb1c10b..eb93369 100644 --- a/src/Records/Classes/CountResult.cs +++ b/src/Records/Classes/CountResult.cs @@ -1,29 +1,29 @@ - + using LiteDB; namespace DiscordConnector.Records { public class CountResult { - public string Name{get;} - public int Count{get;} - - [BsonCtor] - public CountResult(string name, int count) - { - - Name = name; - Count = count; - } + public string Name { get; } + public int Count { get; } - public static int CompareByCount(CountResult cr1, CountResult cr2) - { - return cr1.Count.CompareTo(cr2.Count); - } + [BsonCtor] + public CountResult(string name, int count) + { - public static int CompareByName(CountResult cr1, CountResult cr2) - { - return cr1.Name.CompareTo(cr2.Name); - } + Name = name; + Count = count; + } + + public static int CompareByCount(CountResult cr1, CountResult cr2) + { + return cr1.Count.CompareTo(cr2.Count); + } + + public static int CompareByName(CountResult cr1, CountResult cr2) + { + return cr1.Name.CompareTo(cr2.Name); + } } } diff --git a/src/Records/Database.cs b/src/Records/Database.cs index 2f12cbf..40a7e94 100644 --- a/src/Records/Database.cs +++ b/src/Records/Database.cs @@ -75,7 +75,7 @@ private List CountAllRecordsGroupBySteamId(ILiteCollection RetrieveAllRecordsGroupByName(ILiteCollection ConvertBsonDocumentCountToDotNet(List bs { if (doc.ContainsKey("Name") && doc.ContainsKey("Count")) { - results.Add(new CountResult ( + results.Add(new CountResult( doc["Name"].AsString, doc["Count"].AsInt32 )); @@ -107,7 +107,7 @@ private List ConvertBsonDocumentCountToDotNet(List bs } public List RetrieveAllRecordsGroupByName(string key) - { + { switch (key) { case Categories.Death: @@ -126,7 +126,7 @@ public List RetrieveAllRecordsGroupByName(string key) } } public List CountAllRecordsGroupBySteamId(string key) - { + { switch (key) { case Categories.Death: @@ -168,7 +168,7 @@ public int CountOfRecordsByName(string key, string playerName) return -2; } } - + public int CountOfRecordsBySteamId(string key, ulong steamId) { if (!Plugin.StaticConfig.CollectStatsEnabled) @@ -192,10 +192,10 @@ public int CountOfRecordsBySteamId(string key, ulong steamId) return -2; } } - + public void InsertSimpleStatRecord(string key, string playerName, ulong steamId, Vector3 pos) { - + switch (key) { case Categories.Death: diff --git a/src/Records/RecordsHelper.cs b/src/Records/RecordsHelper.cs index 7b09388..1761ffa 100644 --- a/src/Records/RecordsHelper.cs +++ b/src/Records/RecordsHelper.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections.Generic; using UnityEngine; @@ -30,6 +30,7 @@ public static class Helper public static List TopNResultForCategory(string key, int n) { List queryResults = Plugin.StaticDatabase.RetrieveAllRecordsGroupByName(key); + Plugin.StaticLogger.LogDebug($"TopNResultForCategory {key} n={n}, results={queryResults.Count}"); if (queryResults.Count == 0) { return queryResults; @@ -48,13 +49,19 @@ public static List TopNResultForCategory(string key, int n) public static CountResult TopResultForCategory(string key) { - return Helper.TopNResultForCategory(key, 1)[0]; + var results = Helper.TopNResultForCategory(key, 1); + if (results.Count == 0) + { + return new CountResult("", 0); + } + return results[0]; } public static List BottomNResultForCategory(string key, int n) { - + List queryResults = Plugin.StaticDatabase.RetrieveAllRecordsGroupByName(key); + Plugin.StaticLogger.LogDebug($"BottomNResultForCategory {key} n={n}, results={queryResults.Count}"); if (queryResults.Count == 0) { return queryResults; @@ -72,7 +79,12 @@ public static List BottomNResultForCategory(string key, int n) public static CountResult BottomResultForCategory(string key) { - return Helper.BottomNResultForCategory(key, 1)[0]; + var results = Helper.BottomNResultForCategory(key, 1); + if (results.Count == 0) + { + return new CountResult("", 0); + } + return results[0]; } } - } +} From ca97e1a152901f4f92d561b0ea6301789c349310 Mon Sep 17 00:00:00 2001 From: Nicholas Westerhausen <2317381+nwesterhausen@users.noreply.github.com> Date: Wed, 27 Oct 2021 17:38:50 -0400 Subject: [PATCH 06/15] replace public IP in all messages --- src/MessageTransformer.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/MessageTransformer.cs b/src/MessageTransformer.cs index 84bfcfd..eba31b0 100644 --- a/src/MessageTransformer.cs +++ b/src/MessageTransformer.cs @@ -34,12 +34,12 @@ private static string ReplaceVariables(string rawMessage) .Replace(VAR_6, Plugin.StaticConfig.UserVariable6) .Replace(VAR_7, Plugin.StaticConfig.UserVariable7) .Replace(VAR_8, Plugin.StaticConfig.UserVariable8) - .Replace(VAR_9, Plugin.StaticConfig.UserVariable9); + .Replace(VAR_9, Plugin.StaticConfig.UserVariable9) + .Replace(PUBLIC_IP, Plugin.PublicIpAddress); } public static string FormatServerMessage(string rawMessage) { - return MessageTransformer.ReplaceVariables(rawMessage) - .Replace(PUBLIC_IP, Plugin.PublicIpAddress); + return MessageTransformer.ReplaceVariables(rawMessage); } public static string FormatPlayerMessage(string rawMessage, string playerName) From 61d7e02da2e09989476fc7e6c89611b9d1f1e1b2 Mon Sep 17 00:00:00 2001 From: Nicholas Westerhausen <2317381+nwesterhausen@users.noreply.github.com> Date: Wed, 27 Oct 2021 18:03:01 -0400 Subject: [PATCH 07/15] custom messages for leaderboard heading --- src/Config/MessagesConfig.cs | 41 +++++++++++++++++++++++++++++++ src/Config/PluginConfig.cs | 6 +++++ src/Leaderboard/BottomPlayer.cs | 1 + src/Leaderboard/OverallHighest.cs | 1 + src/Leaderboard/OverallLowest.cs | 1 + src/Leaderboard/TopPlayers.cs | 13 +++++----- src/MessageTransformer.cs | 11 +++++++++ 7 files changed, 67 insertions(+), 7 deletions(-) diff --git a/src/Config/MessagesConfig.cs b/src/Config/MessagesConfig.cs index a9b53be..a416bd6 100644 --- a/src/Config/MessagesConfig.cs +++ b/src/Config/MessagesConfig.cs @@ -14,6 +14,7 @@ internal class MessagesConfig private const string PLAYER_MESSAGES = "Messages.Player"; private const string PLAYER_FIRSTS_MESSAGES = "Messages.PlayerFirsts"; private const string EVENT_MESSAGES = "Messages.Events"; + private const string BOARD_MESSAGES = "Messages.Leaderbaords"; // Server Messages private ConfigEntry serverLaunchMessage; @@ -42,6 +43,12 @@ internal class MessagesConfig private ConfigEntry eventStopMessage; private ConfigEntry eventResumedMessage; + // Board Messages + private ConfigEntry leaderboardTopPlayersMessage; + private ConfigEntry leaderboardBottomPlayersMessage; + private ConfigEntry leaderboardHighestPlayerMessage; + private ConfigEntry leaderboardLowestPlayerMessage; + public MessagesConfig(ConfigFile configFile) { config = configFile; @@ -173,6 +180,28 @@ private void LoadConfig() "The special string %EVENT_END_MSG% will be replaced with the message that is displayed on the screen when the event ends."); // + Environment.NewLine + // "The special string %PLAYERS% will be replaced with a list of players in the event area."); //! Removed due to unreliability + // Board Messages + leaderboardTopPlayersMessage = config.Bind(BOARD_MESSAGES, + "Leaderboard Heading for Top N Players", + "Top %N% Player Leaderboards:", + "Set the message that is included as a heading when this leaderboard is sent." + Environment.NewLine + + "Include %N% to include the number of rankings returned (the configured number)"); + leaderboardBottomPlayersMessage = config.Bind(BOARD_MESSAGES, + "Leaderboard Heading for Bottom N Players", + "Bottom %N% Player Leaderboards:", + "Set the message that is included as a heading when this leaderboard is sent." + Environment.NewLine + + "Include %N% to include the number of rankings returned (the configured number)"); + leaderboardHighestPlayerMessage = config.Bind(BOARD_MESSAGES, + "Leaderboard Heading for Highest Player", + "Top Performer", + "Set the message that is included as a heading when this leaderboard is sent." + Environment.NewLine + + "Include %N% to include the number of rankings returned (the configured number)"); + leaderboardLowestPlayerMessage = config.Bind(BOARD_MESSAGES, + "Leaderboard Heading for Lowest Player", + "Bottom Performer", + "Set the message that is included as a heading when this leaderboard is sent." + Environment.NewLine + + "Include %N% to include the number of rankings returned (the configured number)"); + config.Save(); } @@ -209,6 +238,13 @@ public string ConfigAsJson() jsonString += $"\"eventPausedMessage\":\"{eventPausedMessage.Value}\","; jsonString += $"\"eventResumedMessage\":\"{eventResumedMessage.Value}\","; jsonString += $"\"eventStopMessage\":\"{eventStopMessage.Value}\""; + jsonString += "},"; + + jsonString += $"\"{BOARD_MESSAGES}\":{{"; + jsonString += $"\"leaderboardTopPlayersMessage\":\"{leaderboardTopPlayersMessage.Value}\","; + jsonString += $"\"leaderboardBottomPlayersMessage\":\"{leaderboardBottomPlayersMessage.Value}\","; + jsonString += $"\"leaderboardHighestPlayerMessage\":\"{leaderboardHighestPlayerMessage.Value}\","; + jsonString += $"\"leaderboardLowestPlayerMessage\":\"{leaderboardLowestPlayerMessage.Value}\""; jsonString += "}"; jsonString += "}"; @@ -254,5 +290,10 @@ private static string GetRandomStringFromValue(ConfigEntry configEntry) public string EventStopMesssage => GetRandomStringFromValue(eventStopMessage); public string EventResumedMesssage => GetRandomStringFromValue(eventResumedMessage); + // Messages.Leaderboards + public string LeaderboardTopPlayerHeading => GetRandomStringFromValue(leaderboardTopPlayersMessage); + public string LeaderboardBottomPlayersHeading => GetRandomStringFromValue(leaderboardBottomPlayersMessage); + public string LeaderboardHighestHeading => GetRandomStringFromValue(leaderboardHighestPlayerMessage); + public string LeaderboardLowestHeading => GetRandomStringFromValue(leaderboardLowestPlayerMessage); } } diff --git a/src/Config/PluginConfig.cs b/src/Config/PluginConfig.cs index 6b6cb02..ae16293 100644 --- a/src/Config/PluginConfig.cs +++ b/src/Config/PluginConfig.cs @@ -158,6 +158,12 @@ public void ReloadConfig() public bool DebugEveryEventChange => togglesConfig.DebugEveryEventChange; public bool DebugHttpRequestResponse => togglesConfig.DebugHttpRequestResponse; + // Leaderboard Messages + public string LeaderboardTopPlayerHeading => messagesConfig.LeaderboardTopPlayerHeading; + public string LeaderboardBottomPlayersHeading => messagesConfig.LeaderboardBottomPlayersHeading; + public string LeaderboardHighestHeading => messagesConfig.LeaderboardHighestHeading; + public string LeaderboardLowestHeading => messagesConfig.LeaderboardLowestHeading; + public string ConfigAsJson() { string jsonString = "{"; diff --git a/src/Leaderboard/BottomPlayer.cs b/src/Leaderboard/BottomPlayer.cs index 105ce26..34969ff 100644 --- a/src/Leaderboard/BottomPlayer.cs +++ b/src/Leaderboard/BottomPlayer.cs @@ -34,6 +34,7 @@ public override void SendLeaderboard() } if (leaderFields.Count > 0) { + string discordContent = MessageTransformer.FormatLeaderboardHeader(Plugin.StaticConfig.LeaderboardBottomPlayersHeading, Plugin.StaticConfig.IncludedNumberOfRankings); DiscordApi.SendMessageWithFields("Current Bottom player leaderboard:", leaderFields); } else diff --git a/src/Leaderboard/OverallHighest.cs b/src/Leaderboard/OverallHighest.cs index d349954..49a7021 100644 --- a/src/Leaderboard/OverallHighest.cs +++ b/src/Leaderboard/OverallHighest.cs @@ -31,6 +31,7 @@ public override void SendLeaderboard() } if (leaderFields.Count > 0) { + string discordContent = MessageTransformer.FormatLeaderboardHeader(Plugin.StaticConfig.LeaderboardHighestHeading); DiscordApi.SendMessageWithFields("Current Highest stat leader board:", leaderFields); } else diff --git a/src/Leaderboard/OverallLowest.cs b/src/Leaderboard/OverallLowest.cs index ca67d28..e4ff8cb 100644 --- a/src/Leaderboard/OverallLowest.cs +++ b/src/Leaderboard/OverallLowest.cs @@ -31,6 +31,7 @@ public override void SendLeaderboard() } if (leaderFields.Count > 0) { + string discordContent = MessageTransformer.FormatLeaderboardHeader(Plugin.StaticConfig.LeaderboardLowestHeading); DiscordApi.SendMessageWithFields("Current leaderboard least stats:", leaderFields); } else diff --git a/src/Leaderboard/TopPlayers.cs b/src/Leaderboard/TopPlayers.cs index 88927a7..4ed89e7 100644 --- a/src/Leaderboard/TopPlayers.cs +++ b/src/Leaderboard/TopPlayers.cs @@ -14,27 +14,26 @@ public override void SendLeaderboard() var shouts = Records.Helper.TopNResultForCategory(Records.Categories.Shout, Plugin.StaticConfig.IncludedNumberOfRankings); var pings = Records.Helper.TopNResultForCategory(Records.Categories.Ping, Plugin.StaticConfig.IncludedNumberOfRankings); - - if (Plugin.StaticConfig.RankedDeathLeaderboardEnabled && deaths.Count > 0) { - leaderFields.Add(Tuple.Create("Top Deaths", Leaderboard.RankedCountResultToString(deaths))); + leaderFields.Add(Tuple.Create("Deaths", Leaderboard.RankedCountResultToString(deaths))); } if (Plugin.StaticConfig.RankedSessionLeaderboardEnabled && sessions.Count > 0) { - leaderFields.Add(Tuple.Create("Top Sessions", Leaderboard.RankedCountResultToString(sessions))); + leaderFields.Add(Tuple.Create("Sessions", Leaderboard.RankedCountResultToString(sessions))); } if (Plugin.StaticConfig.RankedShoutLeaderboardEnabled && shouts.Count > 0) { - leaderFields.Add(Tuple.Create("Top Shouts", Leaderboard.RankedCountResultToString(shouts))); + leaderFields.Add(Tuple.Create("Shouts", Leaderboard.RankedCountResultToString(shouts))); } if (Plugin.StaticConfig.RankedPingLeaderboardEnabled && pings.Count > 0) { - leaderFields.Add(Tuple.Create("Top Pings", Leaderboard.RankedCountResultToString(pings))); + leaderFields.Add(Tuple.Create("Pings", Leaderboard.RankedCountResultToString(pings))); } if (leaderFields.Count > 0) { - DiscordApi.SendMessageWithFields("Current top player leaderboard:", leaderFields); + string discordContent = MessageTransformer.FormatLeaderboardHeader(Plugin.StaticConfig.LeaderboardTopPlayerHeading, Plugin.StaticConfig.IncludedNumberOfRankings); + DiscordApi.SendMessageWithFields(discordContent, leaderFields); } else { diff --git a/src/MessageTransformer.cs b/src/MessageTransformer.cs index eba31b0..6d3aa86 100644 --- a/src/MessageTransformer.cs +++ b/src/MessageTransformer.cs @@ -22,6 +22,7 @@ internal static class MessageTransformer private const string EVENT_END_MSG = "%EVENT_END_MSG%"; private const string EVENT_MSG = "%EVENT_MSG%"; private const string EVENT_PLAYERS = "%PLAYERS%"; + private const string N = "%N%"; private static string ReplaceVariables(string rawMessage) { return rawMessage @@ -95,5 +96,15 @@ public static string FormatEventEndMessage(string rawMessage, string eventStartM return MessageTransformer.FormatEventMessage(rawMessage, eventStartMsg, eventEndMsg, pos) .Replace(EVENT_MSG, eventEndMsg); } + public static string FormatLeaderboardHeader(string rawMessage) + { + return MessageTransformer.ReplaceVariables(rawMessage); + } + + public static string FormatLeaderboardHeader(string rawMessage, int n) + { + return MessageTransformer.ReplaceVariables(rawMessage) + .Replace(N, n.ToString()); + } } } From 06d5fdd50e40760f3409c1a6b3ba11bfc882f982 Mon Sep 17 00:00:00 2001 From: Nicholas Westerhausen <2317381+nwesterhausen@users.noreply.github.com> Date: Wed, 27 Oct 2021 18:06:46 -0400 Subject: [PATCH 08/15] fix json config --- src/Config/PluginConfig.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Config/PluginConfig.cs b/src/Config/PluginConfig.cs index ae16293..4ee0934 100644 --- a/src/Config/PluginConfig.cs +++ b/src/Config/PluginConfig.cs @@ -170,7 +170,7 @@ public string ConfigAsJson() jsonString += $"\"Config.Main\":{mainConfig.ConfigAsJson()},"; jsonString += $"\"Config.Messages\":{messagesConfig.ConfigAsJson()},"; - jsonString += $"\"Config.Toggles\":{togglesConfig.ConfigAsJson()}"; + jsonString += $"\"Config.Toggles\":{togglesConfig.ConfigAsJson()},"; jsonString += $"\"Config.Variables\":{variableConfig.ConfigAsJson()}"; jsonString += "}"; From f3a6f1990ae1ab4b4afeee04ef85a4ebbff2d2ff Mon Sep 17 00:00:00 2001 From: Nicholas Westerhausen <2317381+nwesterhausen@users.noreply.github.com> Date: Wed, 27 Oct 2021 18:09:38 -0400 Subject: [PATCH 09/15] actually send formatted message --- src/Leaderboard/BottomPlayer.cs | 2 +- src/Leaderboard/OverallHighest.cs | 2 +- src/Leaderboard/OverallLowest.cs | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/Leaderboard/BottomPlayer.cs b/src/Leaderboard/BottomPlayer.cs index 34969ff..e5a2ca7 100644 --- a/src/Leaderboard/BottomPlayer.cs +++ b/src/Leaderboard/BottomPlayer.cs @@ -35,7 +35,7 @@ public override void SendLeaderboard() if (leaderFields.Count > 0) { string discordContent = MessageTransformer.FormatLeaderboardHeader(Plugin.StaticConfig.LeaderboardBottomPlayersHeading, Plugin.StaticConfig.IncludedNumberOfRankings); - DiscordApi.SendMessageWithFields("Current Bottom player leaderboard:", leaderFields); + DiscordApi.SendMessageWithFields(discordContent, leaderFields); } else { diff --git a/src/Leaderboard/OverallHighest.cs b/src/Leaderboard/OverallHighest.cs index 49a7021..1d3e0d5 100644 --- a/src/Leaderboard/OverallHighest.cs +++ b/src/Leaderboard/OverallHighest.cs @@ -32,7 +32,7 @@ public override void SendLeaderboard() if (leaderFields.Count > 0) { string discordContent = MessageTransformer.FormatLeaderboardHeader(Plugin.StaticConfig.LeaderboardHighestHeading); - DiscordApi.SendMessageWithFields("Current Highest stat leader board:", leaderFields); + DiscordApi.SendMessageWithFields(discordContent, leaderFields); } else { diff --git a/src/Leaderboard/OverallLowest.cs b/src/Leaderboard/OverallLowest.cs index e4ff8cb..32daefc 100644 --- a/src/Leaderboard/OverallLowest.cs +++ b/src/Leaderboard/OverallLowest.cs @@ -32,7 +32,7 @@ public override void SendLeaderboard() if (leaderFields.Count > 0) { string discordContent = MessageTransformer.FormatLeaderboardHeader(Plugin.StaticConfig.LeaderboardLowestHeading); - DiscordApi.SendMessageWithFields("Current leaderboard least stats:", leaderFields); + DiscordApi.SendMessageWithFields(discordContent, leaderFields); } else { From a83097d92a49c51f39b769305f3c819ee92b0b95 Mon Sep 17 00:00:00 2001 From: Nicholas Westerhausen <2317381+nwesterhausen@users.noreply.github.com> Date: Wed, 27 Oct 2021 18:12:54 -0400 Subject: [PATCH 10/15] let header describe it --- src/Leaderboard/BottomPlayer.cs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/Leaderboard/BottomPlayer.cs b/src/Leaderboard/BottomPlayer.cs index e5a2ca7..eaafa92 100644 --- a/src/Leaderboard/BottomPlayer.cs +++ b/src/Leaderboard/BottomPlayer.cs @@ -18,19 +18,19 @@ public override void SendLeaderboard() if (Plugin.StaticConfig.RankedDeathLeaderboardEnabled && deaths.Count > 0) { - leaderFields.Add(Tuple.Create("Bottom Deaths", Leaderboard.RankedCountResultToString(deaths))); + leaderFields.Add(Tuple.Create("Deaths", Leaderboard.RankedCountResultToString(deaths))); } if (Plugin.StaticConfig.RankedSessionLeaderboardEnabled && sessions.Count > 0) { - leaderFields.Add(Tuple.Create("Bottom Sessions", Leaderboard.RankedCountResultToString(sessions))); + leaderFields.Add(Tuple.Create("Sessions", Leaderboard.RankedCountResultToString(sessions))); } if (Plugin.StaticConfig.RankedShoutLeaderboardEnabled && shouts.Count > 0) { - leaderFields.Add(Tuple.Create("Bottom Shouts", Leaderboard.RankedCountResultToString(shouts))); + leaderFields.Add(Tuple.Create("Shouts", Leaderboard.RankedCountResultToString(shouts))); } if (Plugin.StaticConfig.RankedPingLeaderboardEnabled && pings.Count > 0) { - leaderFields.Add(Tuple.Create("Bottom Pings", Leaderboard.RankedCountResultToString(pings))); + leaderFields.Add(Tuple.Create("Pings", Leaderboard.RankedCountResultToString(pings))); } if (leaderFields.Count > 0) { From 48771624b0e6d99a962ebb8cb940e36cefa8e339 Mon Sep 17 00:00:00 2001 From: Nicholas Westerhausen <2317381+nwesterhausen@users.noreply.github.com> Date: Thu, 28 Oct 2021 08:23:08 -0400 Subject: [PATCH 11/15] add 1-time migration from records.json to litedb --- src/Plugin.cs | 4 +- src/Records.cs | 286 ++++++------------------------------------------- 2 files changed, 38 insertions(+), 252 deletions(-) diff --git a/src/Plugin.cs b/src/Plugin.cs index d85698a..6d2dd4e 100644 --- a/src/Plugin.cs +++ b/src/Plugin.cs @@ -23,9 +23,11 @@ public Plugin() { StaticLogger = Logger; StaticConfig = new PluginConfig(Config); - StaticRecords = new RecordsOld(Paths.GameRootPath); StaticDatabase = new Records.Database(Paths.GameRootPath); StaticLeaderboards = new Leaderboard(); + + //! Remove in next major version + StaticRecords = new RecordsOld(Paths.GameRootPath); } private void Awake() diff --git a/src/Records.cs b/src/Records.cs index 1a5e11f..b80dd29 100644 --- a/src/Records.cs +++ b/src/Records.cs @@ -1,9 +1,7 @@ using System; using System.Collections.Generic; using System.IO; -using System.Threading.Tasks; using Newtonsoft.Json; -using UnityEngine; namespace DiscordConnector { @@ -26,8 +24,9 @@ internal class Record class RecordsOld { private static string DEFAULT_FILENAME = "records.json"; - private string storepath; - private bool saveEnabled; + private const string MIGRATED_FILENAME = "records.json.migrated"; + private string storepath, movepath; + // private bool saveEnabled; private List recordCache; /// @@ -43,239 +42,22 @@ public RecordsOld(string basePath, string fileName = null) fileName = DEFAULT_FILENAME; } storepath = Path.Combine(basePath, fileName); - saveEnabled = true; - PopulateCache(); - if (!Plugin.StaticConfig.CollectStatsEnabled) - { - Plugin.StaticLogger.LogInfo("Saving stats is disabled, nothing will be recorded."); - } - } - - - /// - /// Add to a record for under in the records database. - /// This will not save the record if the is not one defined in Records.Categories. - /// - /// Records.Categories category to store the value under - /// The player's name. - /// How much to increase current stored value by. - // public void Store(string key, string playername, int value) - // { - // if (Plugin.StaticConfig.CollectStatsEnabled) - // { - // if (Array.IndexOf(Records.Categories.All, key) >= 0) - // { - // foreach (Record r in recordCache) - // { - // if (r.Category.Equals(key)) - // { - // bool stored = false; - // foreach (RecordValue v in r.Values) - // { - // if (v.Key.Equals(playername)) - // { - // v.Value += value; - // stored = true; - // } - // } - // if (!stored) - // { - // r.Values.Add(new RecordValue() - // { - // Key = playername, - // Value = value - // }); - // } - // } - // } - // // After adding new data, flush data to disk. - // FlushCache().ContinueWith( - // t => Plugin.StaticLogger.LogWarning(t.Exception), - // TaskContinuationOptions.OnlyOnFaulted); - // } - // else - // { - // Plugin.StaticLogger.LogWarning($"Unable to store record of {key} for player {playername} - not considered a valid category."); - // } - // } - // } - /// - /// Get the value stored under at . - /// - /// The Records.Categories category the value is stored under - /// The name of the player - /// This will return 0 if there is no record found for that player. It will return -1 if the category is invalid. - // public int Retrieve(string key, string playername) - // { - // if (!Plugin.StaticConfig.CollectStatsEnabled) - // { - // return -1; - // } - // if (Array.IndexOf(Records.Categories.All, key) >= 0) - // { - // foreach (Record r in recordCache) - // { - // if (r.Category.Equals(key)) - // { - // foreach (RecordValue v in r.Values) - // { - // if (v.Key.Equals(playername)) - // { - // return v.Value; - // } - // } - // } - // } - // } - // Plugin.StaticLogger.LogWarning($"No stored record for player {playername} under {key}, returning default of 0."); - // return 0; - // } - - /// - /// Retrieve all stored values under . - /// - /// Records.Categories category to retrieve stored values from - /// A list of (playername, value) tuples. The list will have length 0 if there are no stored records. - public List> RetrieveAll(string key) - { - List> results = new List>(); - if (!Plugin.StaticConfig.CollectStatsEnabled) - { - return results; - } - - if (Array.IndexOf(Records.Categories.All, key) >= 0) - { - foreach (Record r in recordCache) - { - if (r.Category.Equals(key)) - { - foreach (RecordValue v in r.Values) - { - results.Add(Tuple.Create( - v.Key, v.Value - )); - } - } - } - } + movepath = Path.Combine(basePath, MIGRATED_FILENAME); - return results; + MigrateRecords(); } - /// - /// Retrieve the highest stored value under . - /// - /// Records.Categories category to retrieve stored values from - /// A single (playername, value) tuple. - public Tuple RetrieveHighest(string key) + private void MigrateRecords() { - if (!Plugin.StaticConfig.CollectStatsEnabled) + //! check if storePath exists.. + if (System.IO.File.Exists(storepath)) { - return Tuple.Create("not allowed", -1); - } + Plugin.StaticLogger.LogInfo("Migrating from discovered Records.json to LiteDB"); + //! read all records in from storePath if they exist - if (Array.IndexOf(Records.Categories.All, key) >= 0) - { - string player = "no result"; - int records = -1; - foreach (Record r in recordCache) - { - if (r.Category.Equals(key)) - { - foreach (RecordValue v in r.Values) - { - if (v.Value > records) - { - player = v.Key; - records = v.Value; - } - } - } - } - return Tuple.Create(player, records); - } - else - { - return Tuple.Create($"not recording for {key}", -1); - } - } - - /// - /// Retrieve the lowest stored value under . - /// - /// Records.Categories category to retrieve stored values from - /// A single (playername, value) tuple. - public Tuple RetrieveLowest(string key) - { - if (!Plugin.StaticConfig.CollectStatsEnabled) - { - return Tuple.Create("not allowed", -1); - } - - if (Array.IndexOf(Records.Categories.All, key) >= 0) - { - string player = "no result"; - int records = int.MaxValue; - foreach (Record r in recordCache) - { - if (r.Category.Equals(key)) - { - foreach (RecordValue v in r.Values) - { - if (v.Value < records) - { - player = v.Key; - records = v.Value; - } - } - if (r.Values.Count == 0) - { - records = -1; - } - } - } - return Tuple.Create(player, records); - } - else - { - return Tuple.Create($"not recording for {key}", -1); - } - } - - /// - /// (Asynchronous) Writes the in-memory cache of records to disk. - /// - private async Task FlushCache() - { - if (!saveEnabled) - { - Plugin.StaticLogger.LogDebug("Saving records is disabled due to an error at load time."); - return; - } - if (Plugin.StaticConfig.CollectStatsEnabled) - { - string jsonString = JsonConvert.SerializeObject(recordCache); - - using (var stream = new StreamWriter(@storepath, false)) - { - await stream.WriteAsync(jsonString); - } - - Plugin.StaticLogger.LogDebug($"Flushed cached stats to {storepath}"); - } - } - - /// - /// Builds the in-memory cache by reading from disk. - /// - private void PopulateCache() - { - if (File.Exists(storepath)) - { - string jsonString = File.ReadAllText(@storepath); try { + string jsonString = File.ReadAllText(@storepath); recordCache = JsonConvert.DeserializeObject>(jsonString); Plugin.StaticLogger.LogInfo($"Read existing stats from disk {storepath}"); } @@ -283,38 +65,40 @@ private void PopulateCache() { Plugin.StaticLogger.LogWarning($"No content found when reading {storepath} to read saved records. We will start with default values for all records."); Plugin.StaticLogger.LogDebug("File contained null and threw ArgumentNullException"); - InitializeEmptyCache(); + return; } catch (JsonException) { Plugin.StaticLogger.LogError($"Unable to parse the contents of {storepath} as JSON."); Plugin.StaticLogger.LogError("No records will be recorded to disk until existing file is moved, renamed, or deleted."); - saveEnabled = false; + return; + } + + //! store records into LiteDB + foreach (Record r in recordCache) + { + int count = 0; + foreach (RecordValue v in r.Values) + { + for (int i = 0; i < v.Value; i++) + { + Plugin.StaticDatabase.InsertSimpleStatRecord(r.Category, v.Key, 1); + count++; + } + + } + Plugin.StaticLogger.LogInfo($"Migrated {count} {r.Category} records"); + } + + //! move storePath to a new path with MIGRATED_FILENAME + System.IO.File.Move(storepath, movepath); + Plugin.StaticLogger.LogInfo($"Moved existing records.json to {MIGRATED_FILENAME}"); } else { - Plugin.StaticLogger.LogInfo($"Unable to find existing stats data at {storepath}. Creating new {DEFAULT_FILENAME}"); - InitializeEmptyCache(); - } - } - - private void InitializeEmptyCache() - { - recordCache = new List(); - foreach (string category in Records.Categories.All) - { - recordCache.Add(new Record - { - Category = category, - Values = new List() - }); + Plugin.StaticLogger.LogDebug("No records.json found, not migrating."); } - FlushCache().ContinueWith( - t => Plugin.StaticLogger.LogWarning(t.Exception), - TaskContinuationOptions.OnlyOnFaulted); } - - public Comparison> HighToLowSort = (x, y) => y.Item2.CompareTo(x.Item2); } } From 6434fe101c26764b04d4fc3aa9aaff47184610c1 Mon Sep 17 00:00:00 2001 From: Nicholas Westerhausen <2317381+nwesterhausen@users.noreply.github.com> Date: Tue, 26 Oct 2021 16:16:15 -0400 Subject: [PATCH 12/15] migrate to LiteDB from custom JSON file Features: - Using LiteDB for record storage. Because of how unreliable storing the records in a "roll-your-own" database with a JSON file was, and because of the increased flexibility in what could be stored, I've changed the storage system for the recorded player stats to use LiteDB. Currently this means records for join/leave/death/shout/ping will be timestamped, include the position of the event, have the player name, and the player's steamid. Hopefully adding this additional information will allow for more customization options for the users of this mod. It is set up to do a migration on first load of the updated plugin, the steps it follows for that is: 1. check if records.json (or configured name) exists 2. read all records from the file 3. parse the records 4. loop through all the records and add them to the database Records added this way will have position of zero and a steamid of 1. 5. move the records.json file to records.json.migrated If you don't want to have it auto-migrate the records, rename your records.json or delete it. If the name does not match exactly it will not migrate the data. For the migration steps, it will be outputting log information (at INFO level) with how many records were migrated and which steps completed. - Ranked Lowest Player Leaderbaord Added an inverse of the Top Player leaderboard. - Custom leaderboard heading messages Added configuration for the messages sent at the top of the leaderboard messages. - The variable `%PUBLICIP%` can be used in _any_ message configuration now. --- DiscordConnector.csproj | 2 +- src/Config/MessagesConfig.cs | 41 ++++ src/Config/PluginConfig.cs | 8 +- src/Leaderboard/BottomPlayer.cs | 46 +++++ src/Leaderboard/Leaderboard.cs | 25 ++- src/Leaderboard/OverallHighest.cs | 27 +-- src/Leaderboard/OverallLowest.cs | 27 +-- src/Leaderboard/TopPlayers.cs | 45 ++--- src/MessageTransformer.cs | 17 +- src/Patches/ChatPatches.cs | 14 +- src/Patches/ZNetPatches.cs | 192 +++++++++--------- src/Plugin.cs | 12 +- src/Records.cs | 308 ++++------------------------- src/Records/Classes/CountResult.cs | 29 +++ src/Records/Classes/SimpleStat.cs | 67 +++++++ src/Records/Database.cs | 227 +++++++++++++++++++++ src/Records/RecordsHelper.cs | 90 +++++++++ 17 files changed, 738 insertions(+), 439 deletions(-) create mode 100644 src/Leaderboard/BottomPlayer.cs create mode 100644 src/Records/Classes/CountResult.cs create mode 100644 src/Records/Classes/SimpleStat.cs create mode 100644 src/Records/Database.cs create mode 100644 src/Records/RecordsHelper.cs diff --git a/DiscordConnector.csproj b/DiscordConnector.csproj index 6db13cb..b2d430b 100644 --- a/DiscordConnector.csproj +++ b/DiscordConnector.csproj @@ -35,7 +35,7 @@ - + diff --git a/src/Config/MessagesConfig.cs b/src/Config/MessagesConfig.cs index a9b53be..a416bd6 100644 --- a/src/Config/MessagesConfig.cs +++ b/src/Config/MessagesConfig.cs @@ -14,6 +14,7 @@ internal class MessagesConfig private const string PLAYER_MESSAGES = "Messages.Player"; private const string PLAYER_FIRSTS_MESSAGES = "Messages.PlayerFirsts"; private const string EVENT_MESSAGES = "Messages.Events"; + private const string BOARD_MESSAGES = "Messages.Leaderbaords"; // Server Messages private ConfigEntry serverLaunchMessage; @@ -42,6 +43,12 @@ internal class MessagesConfig private ConfigEntry eventStopMessage; private ConfigEntry eventResumedMessage; + // Board Messages + private ConfigEntry leaderboardTopPlayersMessage; + private ConfigEntry leaderboardBottomPlayersMessage; + private ConfigEntry leaderboardHighestPlayerMessage; + private ConfigEntry leaderboardLowestPlayerMessage; + public MessagesConfig(ConfigFile configFile) { config = configFile; @@ -173,6 +180,28 @@ private void LoadConfig() "The special string %EVENT_END_MSG% will be replaced with the message that is displayed on the screen when the event ends."); // + Environment.NewLine + // "The special string %PLAYERS% will be replaced with a list of players in the event area."); //! Removed due to unreliability + // Board Messages + leaderboardTopPlayersMessage = config.Bind(BOARD_MESSAGES, + "Leaderboard Heading for Top N Players", + "Top %N% Player Leaderboards:", + "Set the message that is included as a heading when this leaderboard is sent." + Environment.NewLine + + "Include %N% to include the number of rankings returned (the configured number)"); + leaderboardBottomPlayersMessage = config.Bind(BOARD_MESSAGES, + "Leaderboard Heading for Bottom N Players", + "Bottom %N% Player Leaderboards:", + "Set the message that is included as a heading when this leaderboard is sent." + Environment.NewLine + + "Include %N% to include the number of rankings returned (the configured number)"); + leaderboardHighestPlayerMessage = config.Bind(BOARD_MESSAGES, + "Leaderboard Heading for Highest Player", + "Top Performer", + "Set the message that is included as a heading when this leaderboard is sent." + Environment.NewLine + + "Include %N% to include the number of rankings returned (the configured number)"); + leaderboardLowestPlayerMessage = config.Bind(BOARD_MESSAGES, + "Leaderboard Heading for Lowest Player", + "Bottom Performer", + "Set the message that is included as a heading when this leaderboard is sent." + Environment.NewLine + + "Include %N% to include the number of rankings returned (the configured number)"); + config.Save(); } @@ -209,6 +238,13 @@ public string ConfigAsJson() jsonString += $"\"eventPausedMessage\":\"{eventPausedMessage.Value}\","; jsonString += $"\"eventResumedMessage\":\"{eventResumedMessage.Value}\","; jsonString += $"\"eventStopMessage\":\"{eventStopMessage.Value}\""; + jsonString += "},"; + + jsonString += $"\"{BOARD_MESSAGES}\":{{"; + jsonString += $"\"leaderboardTopPlayersMessage\":\"{leaderboardTopPlayersMessage.Value}\","; + jsonString += $"\"leaderboardBottomPlayersMessage\":\"{leaderboardBottomPlayersMessage.Value}\","; + jsonString += $"\"leaderboardHighestPlayerMessage\":\"{leaderboardHighestPlayerMessage.Value}\","; + jsonString += $"\"leaderboardLowestPlayerMessage\":\"{leaderboardLowestPlayerMessage.Value}\""; jsonString += "}"; jsonString += "}"; @@ -254,5 +290,10 @@ private static string GetRandomStringFromValue(ConfigEntry configEntry) public string EventStopMesssage => GetRandomStringFromValue(eventStopMessage); public string EventResumedMesssage => GetRandomStringFromValue(eventResumedMessage); + // Messages.Leaderboards + public string LeaderboardTopPlayerHeading => GetRandomStringFromValue(leaderboardTopPlayersMessage); + public string LeaderboardBottomPlayersHeading => GetRandomStringFromValue(leaderboardBottomPlayersMessage); + public string LeaderboardHighestHeading => GetRandomStringFromValue(leaderboardHighestPlayerMessage); + public string LeaderboardLowestHeading => GetRandomStringFromValue(leaderboardLowestPlayerMessage); } } diff --git a/src/Config/PluginConfig.cs b/src/Config/PluginConfig.cs index 6b6cb02..4ee0934 100644 --- a/src/Config/PluginConfig.cs +++ b/src/Config/PluginConfig.cs @@ -158,13 +158,19 @@ public void ReloadConfig() public bool DebugEveryEventChange => togglesConfig.DebugEveryEventChange; public bool DebugHttpRequestResponse => togglesConfig.DebugHttpRequestResponse; + // Leaderboard Messages + public string LeaderboardTopPlayerHeading => messagesConfig.LeaderboardTopPlayerHeading; + public string LeaderboardBottomPlayersHeading => messagesConfig.LeaderboardBottomPlayersHeading; + public string LeaderboardHighestHeading => messagesConfig.LeaderboardHighestHeading; + public string LeaderboardLowestHeading => messagesConfig.LeaderboardLowestHeading; + public string ConfigAsJson() { string jsonString = "{"; jsonString += $"\"Config.Main\":{mainConfig.ConfigAsJson()},"; jsonString += $"\"Config.Messages\":{messagesConfig.ConfigAsJson()},"; - jsonString += $"\"Config.Toggles\":{togglesConfig.ConfigAsJson()}"; + jsonString += $"\"Config.Toggles\":{togglesConfig.ConfigAsJson()},"; jsonString += $"\"Config.Variables\":{variableConfig.ConfigAsJson()}"; jsonString += "}"; diff --git a/src/Leaderboard/BottomPlayer.cs b/src/Leaderboard/BottomPlayer.cs new file mode 100644 index 0000000..eaafa92 --- /dev/null +++ b/src/Leaderboard/BottomPlayer.cs @@ -0,0 +1,46 @@ +using System; +using System.Collections.Generic; + +namespace DiscordConnector.Leaderboards +{ + internal class BottomPlayers : Base + { + public override void SendLeaderboard() + { + List> leaderFields = new List>(); + + var deaths = Records.Helper.BottomNResultForCategory(Records.Categories.Death, Plugin.StaticConfig.IncludedNumberOfRankings); + var sessions = Records.Helper.BottomNResultForCategory(Records.Categories.Join, Plugin.StaticConfig.IncludedNumberOfRankings); + var shouts = Records.Helper.BottomNResultForCategory(Records.Categories.Shout, Plugin.StaticConfig.IncludedNumberOfRankings); + var pings = Records.Helper.BottomNResultForCategory(Records.Categories.Ping, Plugin.StaticConfig.IncludedNumberOfRankings); + + + + if (Plugin.StaticConfig.RankedDeathLeaderboardEnabled && deaths.Count > 0) + { + leaderFields.Add(Tuple.Create("Deaths", Leaderboard.RankedCountResultToString(deaths))); + } + if (Plugin.StaticConfig.RankedSessionLeaderboardEnabled && sessions.Count > 0) + { + leaderFields.Add(Tuple.Create("Sessions", Leaderboard.RankedCountResultToString(sessions))); + } + if (Plugin.StaticConfig.RankedShoutLeaderboardEnabled && shouts.Count > 0) + { + leaderFields.Add(Tuple.Create("Shouts", Leaderboard.RankedCountResultToString(shouts))); + } + if (Plugin.StaticConfig.RankedPingLeaderboardEnabled && pings.Count > 0) + { + leaderFields.Add(Tuple.Create("Pings", Leaderboard.RankedCountResultToString(pings))); + } + if (leaderFields.Count > 0) + { + string discordContent = MessageTransformer.FormatLeaderboardHeader(Plugin.StaticConfig.LeaderboardBottomPlayersHeading, Plugin.StaticConfig.IncludedNumberOfRankings); + DiscordApi.SendMessageWithFields(discordContent, leaderFields); + } + else + { + Plugin.StaticLogger.LogInfo("Not sending a leaderboard because theirs either no leaders, or nothing allowed."); + } + } + } +} diff --git a/src/Leaderboard/Leaderboard.cs b/src/Leaderboard/Leaderboard.cs index d928538..f2ac408 100644 --- a/src/Leaderboard/Leaderboard.cs +++ b/src/Leaderboard/Leaderboard.cs @@ -1,4 +1,6 @@ -using System.Timers; +using System; +using System.Collections.Generic; +using System.Timers; namespace DiscordConnector { @@ -7,17 +9,37 @@ internal class Leaderboard private Leaderboards.Base overallHighest; private Leaderboards.Base overallLowest; private Leaderboards.Base topPlayers; + private Leaderboards.Base bottomPlayers; public Leaderboard() { overallHighest = new Leaderboards.OverallHighest(); overallLowest = new Leaderboards.OverallLowest(); topPlayers = new Leaderboards.TopPlayers(); + bottomPlayers = new Leaderboards.BottomPlayers(); } public Leaderboards.Base OverallHighest => overallHighest; public Leaderboards.Base OverallLowest => overallLowest; public Leaderboards.Base TopPlayers => topPlayers; + public Leaderboards.Base BottomPlayers => bottomPlayers; + + + + /// + /// Takes a sorted list and returns a string listing each member on a line prepended with 1, 2, 3, etc. + /// + /// A pre-sorted list of CountResults. + /// String ready to send to discord listing each player and their value. + public static string RankedCountResultToString(List rankings) + { + string res = ""; + for (int i = 0; i < rankings.Count; i++) + { + res += $"{i + 1}: {rankings[i].Name}: {rankings[i].Count}{Environment.NewLine}"; + } + return res; + } } } @@ -30,6 +52,7 @@ internal abstract class Base /// public void SendLeaderboardOnTimer(object sender, ElapsedEventArgs elapsedEventArgs) { + Plugin.StaticLogger.LogDebug($"Running the leaderboard send"); this.SendLeaderboard(); } diff --git a/src/Leaderboard/OverallHighest.cs b/src/Leaderboard/OverallHighest.cs index 3aeb75c..1d3e0d5 100644 --- a/src/Leaderboard/OverallHighest.cs +++ b/src/Leaderboard/OverallHighest.cs @@ -7,31 +7,32 @@ internal class OverallHighest : Base { public override void SendLeaderboard() { - var deathLeader = Plugin.StaticRecords.RetrieveHighest(RecordCategories.Death); - var joinLeader = Plugin.StaticRecords.RetrieveHighest(RecordCategories.Join); - var shoutLeader = Plugin.StaticRecords.RetrieveHighest(RecordCategories.Shout); - var pingLeader = Plugin.StaticRecords.RetrieveHighest(RecordCategories.Ping); + var deathLeader = Records.Helper.TopResultForCategory(Records.Categories.Death); + var joinLeader = Records.Helper.TopResultForCategory(Records.Categories.Join); + var shoutLeader = Records.Helper.TopResultForCategory(Records.Categories.Shout); + var pingLeader = Records.Helper.TopResultForCategory(Records.Categories.Ping); List> leaderFields = new List>(); - if (Plugin.StaticConfig.MostDeathLeaderboardEnabled && deathLeader.Item2 > 0) + if (Plugin.StaticConfig.MostDeathLeaderboardEnabled && deathLeader.Count > 0) { - leaderFields.Add(Tuple.Create("Most Deaths", $"{deathLeader.Item1} ({deathLeader.Item2})")); + leaderFields.Add(Tuple.Create("Most Deaths", $"{deathLeader.Name} ({deathLeader.Count})")); } - if (Plugin.StaticConfig.MostSessionLeaderboardEnabled && joinLeader.Item2 > 0) + if (Plugin.StaticConfig.MostSessionLeaderboardEnabled && joinLeader.Count > 0) { - leaderFields.Add(Tuple.Create("Most Sessions", $"{joinLeader.Item1} ({joinLeader.Item2})")); + leaderFields.Add(Tuple.Create("Most Sessions", $"{joinLeader.Name} ({joinLeader.Count})")); } - if (Plugin.StaticConfig.MostShoutLeaderboardEnabled && shoutLeader.Item2 > 0) + if (Plugin.StaticConfig.MostShoutLeaderboardEnabled && shoutLeader.Count > 0) { - leaderFields.Add(Tuple.Create("Most Shouts", $"{shoutLeader.Item1} ({shoutLeader.Item2})")); + leaderFields.Add(Tuple.Create("Most Shouts", $"{shoutLeader.Name} ({shoutLeader.Count})")); } - if (Plugin.StaticConfig.MostPingLeaderboardEnabled && pingLeader.Item2 > 0) + if (Plugin.StaticConfig.MostPingLeaderboardEnabled && pingLeader.Count > 0) { - leaderFields.Add(Tuple.Create("Most Pings", $"{pingLeader.Item1} ({pingLeader.Item2})")); + leaderFields.Add(Tuple.Create("Most Pings", $"{pingLeader.Name} ({pingLeader.Count})")); } if (leaderFields.Count > 0) { - DiscordApi.SendMessageWithFields("Current Highest stat leader board:", leaderFields); + string discordContent = MessageTransformer.FormatLeaderboardHeader(Plugin.StaticConfig.LeaderboardHighestHeading); + DiscordApi.SendMessageWithFields(discordContent, leaderFields); } else { diff --git a/src/Leaderboard/OverallLowest.cs b/src/Leaderboard/OverallLowest.cs index 6ad46e1..32daefc 100644 --- a/src/Leaderboard/OverallLowest.cs +++ b/src/Leaderboard/OverallLowest.cs @@ -7,31 +7,32 @@ internal class OverallLowest : Base { public override void SendLeaderboard() { - var deathLeader = Plugin.StaticRecords.RetrieveLowest(RecordCategories.Death); - var joinLeader = Plugin.StaticRecords.RetrieveLowest(RecordCategories.Join); - var shoutLeader = Plugin.StaticRecords.RetrieveLowest(RecordCategories.Shout); - var pingLeader = Plugin.StaticRecords.RetrieveLowest(RecordCategories.Ping); + var deathLeader = Records.Helper.BottomResultForCategory(Records.Categories.Death); + var joinLeader = Records.Helper.BottomResultForCategory(Records.Categories.Join); + var shoutLeader = Records.Helper.BottomResultForCategory(Records.Categories.Shout); + var pingLeader = Records.Helper.BottomResultForCategory(Records.Categories.Ping); List> leaderFields = new List>(); - if (Plugin.StaticConfig.LeastDeathLeaderboardEnabled && deathLeader.Item2 > 0) + if (Plugin.StaticConfig.LeastDeathLeaderboardEnabled && deathLeader.Count > 0) { - leaderFields.Add(Tuple.Create("Least Deaths", $"{deathLeader.Item1} ({deathLeader.Item2})")); + leaderFields.Add(Tuple.Create("Least Deaths", $"{deathLeader.Name} ({deathLeader.Count})")); } - if (Plugin.StaticConfig.LeastSessionLeaderboardEnabled && joinLeader.Item2 > 0) + if (Plugin.StaticConfig.LeastSessionLeaderboardEnabled && joinLeader.Count > 0) { - leaderFields.Add(Tuple.Create("Least Sessions", $"{joinLeader.Item1} ({joinLeader.Item2})")); + leaderFields.Add(Tuple.Create("Least Sessions", $"{joinLeader.Name} ({joinLeader.Count})")); } - if (Plugin.StaticConfig.LeastShoutLeaderboardEnabled && shoutLeader.Item2 > 0) + if (Plugin.StaticConfig.LeastShoutLeaderboardEnabled && shoutLeader.Count > 0) { - leaderFields.Add(Tuple.Create("Least Shouts", $"{shoutLeader.Item1} ({shoutLeader.Item2})")); + leaderFields.Add(Tuple.Create("Least Shouts", $"{shoutLeader.Name} ({shoutLeader.Count})")); } - if (Plugin.StaticConfig.LeastPingLeaderboardEnabled && pingLeader.Item2 > 0) + if (Plugin.StaticConfig.LeastPingLeaderboardEnabled && pingLeader.Count > 0) { - leaderFields.Add(Tuple.Create("Least Pings", $"{pingLeader.Item1} ({pingLeader.Item2})")); + leaderFields.Add(Tuple.Create("Least Pings", $"{pingLeader.Name} ({pingLeader.Count})")); } if (leaderFields.Count > 0) { - DiscordApi.SendMessageWithFields("Current leaderboard least stats:", leaderFields); + string discordContent = MessageTransformer.FormatLeaderboardHeader(Plugin.StaticConfig.LeaderboardLowestHeading); + DiscordApi.SendMessageWithFields(discordContent, leaderFields); } else { diff --git a/src/Leaderboard/TopPlayers.cs b/src/Leaderboard/TopPlayers.cs index 31d2d1a..4ed89e7 100644 --- a/src/Leaderboard/TopPlayers.cs +++ b/src/Leaderboard/TopPlayers.cs @@ -7,59 +7,38 @@ internal class TopPlayers : Base { public override void SendLeaderboard() { - var deaths = Plugin.StaticRecords.RetrieveAll(RecordCategories.Death); - var sessions = Plugin.StaticRecords.RetrieveAll(RecordCategories.Join); - var shouts = Plugin.StaticRecords.RetrieveAll(RecordCategories.Shout); - var pings = Plugin.StaticRecords.RetrieveAll(RecordCategories.Ping); - List> leaderFields = new List>(); + + var deaths = Records.Helper.TopNResultForCategory(Records.Categories.Death, Plugin.StaticConfig.IncludedNumberOfRankings); + var sessions = Records.Helper.TopNResultForCategory(Records.Categories.Join, Plugin.StaticConfig.IncludedNumberOfRankings); + var shouts = Records.Helper.TopNResultForCategory(Records.Categories.Shout, Plugin.StaticConfig.IncludedNumberOfRankings); + var pings = Records.Helper.TopNResultForCategory(Records.Categories.Ping, Plugin.StaticConfig.IncludedNumberOfRankings); + if (Plugin.StaticConfig.RankedDeathLeaderboardEnabled && deaths.Count > 0) { - deaths.Sort(Plugin.StaticRecords.HighToLowSort); - leaderFields.Add(Tuple.Create("Top Deaths", TopPlayersFormater(deaths.ToArray()))); + leaderFields.Add(Tuple.Create("Deaths", Leaderboard.RankedCountResultToString(deaths))); } if (Plugin.StaticConfig.RankedSessionLeaderboardEnabled && sessions.Count > 0) { - sessions.Sort(Plugin.StaticRecords.HighToLowSort); - leaderFields.Add(Tuple.Create("Top Sessions", TopPlayersFormater(sessions.ToArray()))); + leaderFields.Add(Tuple.Create("Sessions", Leaderboard.RankedCountResultToString(sessions))); } if (Plugin.StaticConfig.RankedShoutLeaderboardEnabled && shouts.Count > 0) { - shouts.Sort(Plugin.StaticRecords.HighToLowSort); - leaderFields.Add(Tuple.Create("Top Shouts", TopPlayersFormater(shouts.ToArray()))); + leaderFields.Add(Tuple.Create("Shouts", Leaderboard.RankedCountResultToString(shouts))); } if (Plugin.StaticConfig.RankedPingLeaderboardEnabled && pings.Count > 0) { - pings.Sort(Plugin.StaticRecords.HighToLowSort); - leaderFields.Add(Tuple.Create("Top Pings", TopPlayersFormater(pings.ToArray()))); + leaderFields.Add(Tuple.Create("Pings", Leaderboard.RankedCountResultToString(pings))); } if (leaderFields.Count > 0) { - DiscordApi.SendMessageWithFields("Current top player leaderboard:", leaderFields); + string discordContent = MessageTransformer.FormatLeaderboardHeader(Plugin.StaticConfig.LeaderboardTopPlayerHeading, Plugin.StaticConfig.IncludedNumberOfRankings); + DiscordApi.SendMessageWithFields(discordContent, leaderFields); } else { Plugin.StaticLogger.LogInfo("Not sending a leaderboard because theirs either no leaders, or nothing allowed."); } } - - /// - /// Takes a sorted array and returns a string combining the top n results (n as defined in config). - /// - /// A pre-sorted array of (playername, value) Tuples. - /// String ready to send to discord listing each player and their value. - private string TopPlayersFormater(Tuple[] sortedTopPlayers) - { - string result = ""; - for (int i = 0; i < Plugin.StaticConfig.IncludedNumberOfRankings; i++) - { - if (i < sortedTopPlayers.Length) - { - Tuple player = sortedTopPlayers[i]; - result += $"{i + 1}: {player.Item1}: {player.Item2}{Environment.NewLine}"; - } - } - return result; - } } } diff --git a/src/MessageTransformer.cs b/src/MessageTransformer.cs index 84bfcfd..6d3aa86 100644 --- a/src/MessageTransformer.cs +++ b/src/MessageTransformer.cs @@ -22,6 +22,7 @@ internal static class MessageTransformer private const string EVENT_END_MSG = "%EVENT_END_MSG%"; private const string EVENT_MSG = "%EVENT_MSG%"; private const string EVENT_PLAYERS = "%PLAYERS%"; + private const string N = "%N%"; private static string ReplaceVariables(string rawMessage) { return rawMessage @@ -34,12 +35,12 @@ private static string ReplaceVariables(string rawMessage) .Replace(VAR_6, Plugin.StaticConfig.UserVariable6) .Replace(VAR_7, Plugin.StaticConfig.UserVariable7) .Replace(VAR_8, Plugin.StaticConfig.UserVariable8) - .Replace(VAR_9, Plugin.StaticConfig.UserVariable9); + .Replace(VAR_9, Plugin.StaticConfig.UserVariable9) + .Replace(PUBLIC_IP, Plugin.PublicIpAddress); } public static string FormatServerMessage(string rawMessage) { - return MessageTransformer.ReplaceVariables(rawMessage) - .Replace(PUBLIC_IP, Plugin.PublicIpAddress); + return MessageTransformer.ReplaceVariables(rawMessage); } public static string FormatPlayerMessage(string rawMessage, string playerName) @@ -95,5 +96,15 @@ public static string FormatEventEndMessage(string rawMessage, string eventStartM return MessageTransformer.FormatEventMessage(rawMessage, eventStartMsg, eventEndMsg, pos) .Replace(EVENT_MSG, eventEndMsg); } + public static string FormatLeaderboardHeader(string rawMessage) + { + return MessageTransformer.ReplaceVariables(rawMessage); + } + + public static string FormatLeaderboardHeader(string rawMessage, int n) + { + return MessageTransformer.ReplaceVariables(rawMessage) + .Replace(N, n.ToString()); + } } } diff --git a/src/Patches/ChatPatches.cs b/src/Patches/ChatPatches.cs index 4214da4..b5688b2 100644 --- a/src/Patches/ChatPatches.cs +++ b/src/Patches/ChatPatches.cs @@ -16,10 +16,12 @@ private static void Prefix(ref GameObject go, ref long senderID, ref Vector3 pos Plugin.StaticLogger.LogInfo($"Ignored shout from user on muted list. User: {user} Shout: {text}. Index {Plugin.StaticConfig.MutedPlayers.IndexOf(user)}"); return; } + + ulong peerSteamID = ((ZSteamSocket)ZNet.instance.GetPeerByPlayerName(user).m_socket).GetPeerID().m_SteamID; // Get the SteamID from peer. switch (type) { case Talker.Type.Ping: - if (Plugin.StaticConfig.AnnouncePlayerFirstPingEnabled && Plugin.StaticRecords.Retrieve(RecordCategories.Ping, user) == 0) + if (Plugin.StaticConfig.AnnouncePlayerFirstPingEnabled && Plugin.StaticDatabase.CountOfRecordsByName(Records.Categories.Ping, user) == 0) { DiscordApi.SendMessage( MessageTransformer.FormatPlayerMessage(Plugin.StaticConfig.PlayerFirstPingMessage, user) @@ -27,7 +29,7 @@ private static void Prefix(ref GameObject go, ref long senderID, ref Vector3 pos } if (Plugin.StaticConfig.StatsPingEnabled) { - Plugin.StaticRecords.Store(RecordCategories.Ping, user, 1); + Plugin.StaticDatabase.InsertSimpleStatRecord(Records.Categories.Ping, user, peerSteamID, pos); } if (Plugin.StaticConfig.ChatPingEnabled) { @@ -56,7 +58,7 @@ private static void Prefix(ref GameObject go, ref long senderID, ref Vector3 pos { if (!Plugin.IsHeadless()) { - if (Plugin.StaticConfig.AnnouncePlayerFirstJoinEnabled && Plugin.StaticRecords.Retrieve(RecordCategories.Join, user) == 0) + if (Plugin.StaticConfig.AnnouncePlayerFirstJoinEnabled && Plugin.StaticDatabase.CountOfRecordsByName(Records.Categories.Join, user) == 0) { DiscordApi.SendMessage( MessageTransformer.FormatPlayerMessage(Plugin.StaticConfig.PlayerFirstJoinMessage, user) @@ -64,7 +66,7 @@ private static void Prefix(ref GameObject go, ref long senderID, ref Vector3 pos } if (Plugin.StaticConfig.StatsJoinEnabled) { - Plugin.StaticRecords.Store(RecordCategories.Join, user, 1); + Plugin.StaticDatabase.InsertSimpleStatRecord(Records.Categories.Join, user, peerSteamID, pos); } if (Plugin.StaticConfig.PlayerJoinMessageEnabled) { @@ -91,7 +93,7 @@ private static void Prefix(ref GameObject go, ref long senderID, ref Vector3 pos } else { - if (Plugin.StaticConfig.AnnouncePlayerFirstShoutEnabled && Plugin.StaticRecords.Retrieve(RecordCategories.Shout, user) == 0) + if (Plugin.StaticConfig.AnnouncePlayerFirstShoutEnabled && Plugin.StaticDatabase.CountOfRecordsByName(Records.Categories.Shout, user) == 0) { DiscordApi.SendMessage( MessageTransformer.FormatPlayerMessage(Plugin.StaticConfig.PlayerFirstShoutMessage, user, text) @@ -99,7 +101,7 @@ private static void Prefix(ref GameObject go, ref long senderID, ref Vector3 pos } if (Plugin.StaticConfig.StatsShoutEnabled) { - Plugin.StaticRecords.Store(RecordCategories.Shout, user, 1); + Plugin.StaticDatabase.InsertSimpleStatRecord(Records.Categories.Shout, user, peerSteamID, pos); } if (Plugin.StaticConfig.ChatShoutEnabled) { diff --git a/src/Patches/ZNetPatches.cs b/src/Patches/ZNetPatches.cs index 940963c..6ff36e3 100644 --- a/src/Patches/ZNetPatches.cs +++ b/src/Patches/ZNetPatches.cs @@ -50,6 +50,7 @@ private static void Postfix(ZRpc rpc, ZDOID characterID) { return; } + ulong peerSteamID = ((ZSteamSocket)peer.m_socket).GetPeerID().m_SteamID; // Get the SteamID from peer. if (joinedPlayers.IndexOf(peer.m_uid) >= 0) { // Seems that player is dead if character ZDOID id is 0 @@ -61,50 +62,50 @@ private static void Postfix(ZRpc rpc, ZDOID characterID) } if (Plugin.StaticConfig.PlayerDeathMessageEnabled) { - if (Plugin.StaticConfig.AnnouncePlayerFirstDeathEnabled && Plugin.StaticRecords.Retrieve(RecordCategories.Death, peer.m_playerName) == 0) - { - string firstDeathMessage = MessageTransformer.FormatPlayerMessage(Plugin.StaticConfig.PlayerFirstDeathMessage, peer.m_playerName); - if (Plugin.StaticConfig.PlayerDeathPosEnabled) - { - if (Plugin.StaticConfig.DiscordEmbedsEnabled || !firstDeathMessage.Contains("%POS%")) - { - DiscordApi.SendMessage(firstDeathMessage, peer.m_refPos); - } - else - { - DiscordApi.SendMessage(MessageTransformer.FormatPlayerMessage(Plugin.StaticConfig.PlayerFirstDeathMessage, peer.m_playerName, peer.m_refPos)); - } - } - else - { - DiscordApi.SendMessage(firstDeathMessage); - } + if (Plugin.StaticConfig.AnnouncePlayerFirstDeathEnabled && Plugin.StaticDatabase.CountOfRecordsByName(Records.Categories.Death, peer.m_playerName) == 0) + { + string firstDeathMessage = MessageTransformer.FormatPlayerMessage(Plugin.StaticConfig.PlayerFirstDeathMessage, peer.m_playerName); + if (Plugin.StaticConfig.PlayerDeathPosEnabled) + { + if (Plugin.StaticConfig.DiscordEmbedsEnabled || !firstDeathMessage.Contains("%POS%")) + { + DiscordApi.SendMessage(firstDeathMessage, peer.m_refPos); + } + else + { + DiscordApi.SendMessage(MessageTransformer.FormatPlayerMessage(Plugin.StaticConfig.PlayerFirstDeathMessage, peer.m_playerName, peer.m_refPos)); + } + } + else + { + DiscordApi.SendMessage(firstDeathMessage); + } } - else - { - string message = MessageTransformer.FormatPlayerMessage(Plugin.StaticConfig.DeathMessage, peer.m_playerName); - if (Plugin.StaticConfig.PlayerDeathPosEnabled) - { - if (Plugin.StaticConfig.DiscordEmbedsEnabled || !message.Contains("%POS%")) - { - DiscordApi.SendMessage(message, peer.m_refPos); - } - else - { - message = MessageTransformer.FormatPlayerMessage(Plugin.StaticConfig.DeathMessage, peer.m_playerName, peer.m_refPos); - DiscordApi.SendMessage(message); - } - } - else - { - DiscordApi.SendMessage(message); + else + { + string message = MessageTransformer.FormatPlayerMessage(Plugin.StaticConfig.DeathMessage, peer.m_playerName); + if (Plugin.StaticConfig.PlayerDeathPosEnabled) + { + if (Plugin.StaticConfig.DiscordEmbedsEnabled || !message.Contains("%POS%")) + { + DiscordApi.SendMessage(message, peer.m_refPos); + } + else + { + message = MessageTransformer.FormatPlayerMessage(Plugin.StaticConfig.DeathMessage, peer.m_playerName, peer.m_refPos); + DiscordApi.SendMessage(message); + } + } + else + { + DiscordApi.SendMessage(message); } } - } - + } + if (Plugin.StaticConfig.StatsDeathEnabled) { - Plugin.StaticRecords.Store(RecordCategories.Death, peer.m_playerName, 1); + Plugin.StaticDatabase.InsertSimpleStatRecord(Records.Categories.Death, peer.m_playerName, peerSteamID, peer.m_refPos); } } else @@ -114,26 +115,26 @@ private static void Postfix(ZRpc rpc, ZDOID characterID) Plugin.StaticLogger.LogDebug($"Added player {peer.m_uid} ({peer.m_playerName}) to joined player list."); if (Plugin.StaticConfig.PlayerJoinMessageEnabled) { - if (Plugin.StaticConfig.AnnouncePlayerFirstJoinEnabled && Plugin.StaticRecords.Retrieve(RecordCategories.Join, peer.m_playerName) == 0) - { - string firstJoinMessage = MessageTransformer.FormatPlayerMessage(Plugin.StaticConfig.PlayerFirstJoinMessage, peer.m_playerName); - if (Plugin.StaticConfig.PlayerJoinPosEnabled) - { - if (Plugin.StaticConfig.DiscordEmbedsEnabled || !firstJoinMessage.Contains("%POS%")) - { - DiscordApi.SendMessage(firstJoinMessage, peer.m_refPos); - } - else - { - firstJoinMessage = MessageTransformer.FormatPlayerMessage(Plugin.StaticConfig.JoinMessage, peer.m_playerName, peer.m_refPos); - DiscordApi.SendMessage(firstJoinMessage); - } - } - else - { - DiscordApi.SendMessage( - MessageTransformer.FormatPlayerMessage(Plugin.StaticConfig.PlayerFirstJoinMessage, peer.m_playerName)); - } + if (Plugin.StaticConfig.AnnouncePlayerFirstJoinEnabled && Plugin.StaticDatabase.CountOfRecordsByName(Records.Categories.Join, peer.m_playerName) == 0) + { + string firstJoinMessage = MessageTransformer.FormatPlayerMessage(Plugin.StaticConfig.PlayerFirstJoinMessage, peer.m_playerName); + if (Plugin.StaticConfig.PlayerJoinPosEnabled) + { + if (Plugin.StaticConfig.DiscordEmbedsEnabled || !firstJoinMessage.Contains("%POS%")) + { + DiscordApi.SendMessage(firstJoinMessage, peer.m_refPos); + } + else + { + firstJoinMessage = MessageTransformer.FormatPlayerMessage(Plugin.StaticConfig.JoinMessage, peer.m_playerName, peer.m_refPos); + DiscordApi.SendMessage(firstJoinMessage); + } + } + else + { + DiscordApi.SendMessage( + MessageTransformer.FormatPlayerMessage(Plugin.StaticConfig.PlayerFirstJoinMessage, peer.m_playerName)); + } } string message = MessageTransformer.FormatPlayerMessage(Plugin.StaticConfig.JoinMessage, peer.m_playerName); if (Plugin.StaticConfig.PlayerJoinPosEnabled) @@ -142,21 +143,21 @@ private static void Postfix(ZRpc rpc, ZDOID characterID) { DiscordApi.SendMessage(message, peer.m_refPos); } - else - { - message = MessageTransformer.FormatPlayerMessage(Plugin.StaticConfig.JoinMessage, peer.m_playerName, peer.m_refPos); - DiscordApi.SendMessage(message); - } + else + { + message = MessageTransformer.FormatPlayerMessage(Plugin.StaticConfig.JoinMessage, peer.m_playerName, peer.m_refPos); + DiscordApi.SendMessage(message); + } } else { DiscordApi.SendMessage(message); } - } - + } + if (Plugin.StaticConfig.StatsJoinEnabled) { - Plugin.StaticRecords.Store(RecordCategories.Join, peer.m_playerName, 1); + Plugin.StaticDatabase.InsertSimpleStatRecord(Records.Categories.Join, peer.m_playerName, peerSteamID, peer.m_refPos); } } } @@ -172,25 +173,25 @@ private static void Prefix(ZRpc rpc) { if (Plugin.StaticConfig.PlayerLeaveMessageEnabled) { - if (Plugin.StaticConfig.AnnouncePlayerFirstLeaveEnabled && Plugin.StaticRecords.Retrieve(RecordCategories.Leave, peer.m_playerName) == 0) - { - string firstLeaveMessage = MessageTransformer.FormatPlayerMessage(Plugin.StaticConfig.PlayerFirstLeaveMessage, peer.m_playerName); - if (Plugin.StaticConfig.PlayerLeavePosEnabled) - { - if (Plugin.StaticConfig.DiscordEmbedsEnabled || !firstLeaveMessage.Contains("%POS%")) - { - DiscordApi.SendMessage(firstLeaveMessage, peer.m_refPos); - } - else - { - firstLeaveMessage = MessageTransformer.FormatPlayerMessage(Plugin.StaticConfig.PlayerFirstLeaveMessage, peer.m_playerName, peer.m_refPos); - DiscordApi.SendMessage(firstLeaveMessage); - } - } - else - { - DiscordApi.SendMessage(firstLeaveMessage); - } + if (Plugin.StaticConfig.AnnouncePlayerFirstLeaveEnabled && Plugin.StaticDatabase.CountOfRecordsByName(Records.Categories.Leave, peer.m_playerName) == 0) + { + string firstLeaveMessage = MessageTransformer.FormatPlayerMessage(Plugin.StaticConfig.PlayerFirstLeaveMessage, peer.m_playerName); + if (Plugin.StaticConfig.PlayerLeavePosEnabled) + { + if (Plugin.StaticConfig.DiscordEmbedsEnabled || !firstLeaveMessage.Contains("%POS%")) + { + DiscordApi.SendMessage(firstLeaveMessage, peer.m_refPos); + } + else + { + firstLeaveMessage = MessageTransformer.FormatPlayerMessage(Plugin.StaticConfig.PlayerFirstLeaveMessage, peer.m_playerName, peer.m_refPos); + DiscordApi.SendMessage(firstLeaveMessage); + } + } + else + { + DiscordApi.SendMessage(firstLeaveMessage); + } } string message = MessageTransformer.FormatPlayerMessage(Plugin.StaticConfig.LeaveMessage, peer.m_playerName); @@ -200,22 +201,23 @@ private static void Prefix(ZRpc rpc) { DiscordApi.SendMessage(message, peer.m_refPos); } - else - { - message = MessageTransformer.FormatPlayerMessage(Plugin.StaticConfig.LeaveMessage, peer.m_playerName, peer.m_refPos); - DiscordApi.SendMessage(message); - } - + else + { + message = MessageTransformer.FormatPlayerMessage(Plugin.StaticConfig.LeaveMessage, peer.m_playerName, peer.m_refPos); + DiscordApi.SendMessage(message); + } + } else { DiscordApi.SendMessage(message); } - } - + } + if (Plugin.StaticConfig.StatsLeaveEnabled) { - Plugin.StaticRecords.Store(RecordCategories.Leave, peer.m_playerName, 1); + ulong peerSteamID = ((ZSteamSocket)peer.m_socket).GetPeerID().m_SteamID; // Get the SteamID from peer. + Plugin.StaticDatabase.InsertSimpleStatRecord(Records.Categories.Leave, peer.m_playerName, peerSteamID, peer.m_refPos); } } } diff --git a/src/Plugin.cs b/src/Plugin.cs index 4c345e9..6d2dd4e 100644 --- a/src/Plugin.cs +++ b/src/Plugin.cs @@ -1,5 +1,6 @@ using BepInEx; using BepInEx.Logging; +using DiscordConnector.Records; using HarmonyLib; using UnityEngine; using UnityEngine.Rendering; @@ -11,7 +12,8 @@ public class Plugin : BaseUnityPlugin { internal static ManualLogSource StaticLogger; internal static PluginConfig StaticConfig; - internal static Records StaticRecords; + internal static RecordsOld StaticRecords; + internal static Database StaticDatabase; internal static Leaderboard StaticLeaderboards; internal static EventWatcher StaticEventWatcher; internal static string PublicIpAddress; @@ -21,8 +23,11 @@ public Plugin() { StaticLogger = Logger; StaticConfig = new PluginConfig(Config); - StaticRecords = new Records(Paths.GameRootPath); + StaticDatabase = new Records.Database(Paths.GameRootPath); StaticLeaderboards = new Leaderboard(); + + //! Remove in next major version + StaticRecords = new RecordsOld(Paths.GameRootPath); } private void Awake() @@ -49,8 +54,10 @@ private void Awake() leaderboardTimer.Elapsed += StaticLeaderboards.OverallHighest.SendLeaderboardOnTimer; leaderboardTimer.Elapsed += StaticLeaderboards.OverallLowest.SendLeaderboardOnTimer; leaderboardTimer.Elapsed += StaticLeaderboards.TopPlayers.SendLeaderboardOnTimer; + leaderboardTimer.Elapsed += StaticLeaderboards.BottomPlayers.SendLeaderboardOnTimer; // Interval is learned from config file in minutes leaderboardTimer.Interval = 60 * 1000 * StaticConfig.StatsAnnouncementPeriod; + Plugin.StaticLogger.LogDebug($"Enabling leaderboard timers with interval {leaderboardTimer.Interval}ms"); leaderboardTimer.Start(); } @@ -62,6 +69,7 @@ private void Awake() private void OnDestroy() { _harmony.UnpatchSelf(); + StaticDatabase.Dispose(); } /// diff --git a/src/Records.cs b/src/Records.cs index d139d4b..b80dd29 100644 --- a/src/Records.cs +++ b/src/Records.cs @@ -1,31 +1,10 @@ using System; using System.Collections.Generic; using System.IO; -using System.Threading.Tasks; using Newtonsoft.Json; namespace DiscordConnector { - /// - /// These are categories used when keeping track of values in the record system. It is a simple system - /// that currently only supports storing string:integer pairings underneath one of these categories. - /// - public static class RecordCategories - { - public const string Death = "death"; - public const string Join = "join"; - public const string Leave = "leave"; - public const string Ping = "ping"; - public const string Shout = "shout"; - - public static string[] All = new string[] { - Death, - Join, - Leave, - Ping, - Shout - }; - } /// /// The individual key:value pairings used in the record system. It supports only string:int pairings. /// @@ -42,11 +21,12 @@ internal class Record public string Category { get; set; } public List Values { get; set; } } - class Records + class RecordsOld { private static string DEFAULT_FILENAME = "records.json"; - private string storepath; - private bool saveEnabled; + private const string MIGRATED_FILENAME = "records.json.migrated"; + private string storepath, movepath; + // private bool saveEnabled; private List recordCache; /// @@ -55,245 +35,29 @@ class Records /// /// The directory to store the json file of records in. /// The name of the file to use for storage. - public Records(string basePath, string fileName = null) + public RecordsOld(string basePath, string fileName = null) { if (fileName == null) { fileName = DEFAULT_FILENAME; } storepath = Path.Combine(basePath, fileName); - saveEnabled = true; - PopulateCache(); - if (!Plugin.StaticConfig.CollectStatsEnabled) - { - Plugin.StaticLogger.LogInfo("Saving stats is disabled, nothing will be recorded."); - } - } - - /// - /// Add to a record for under in the records database. - /// This will not save the record if the is not one defined in RecordCategories. - /// - /// RecordCategories category to store the value under - /// The player's name. - /// How much to increase current stored value by. - public void Store(string key, string playername, int value) - { - if (Plugin.StaticConfig.CollectStatsEnabled) - { - if (Array.IndexOf(RecordCategories.All, key) >= 0) - { - foreach (Record r in recordCache) - { - if (r.Category.Equals(key)) - { - bool stored = false; - foreach (RecordValue v in r.Values) - { - if (v.Key.Equals(playername)) - { - v.Value += value; - stored = true; - } - } - if (!stored) - { - r.Values.Add(new RecordValue() - { - Key = playername, - Value = value - }); - } - } - } - // After adding new data, flush data to disk. - FlushCache().ContinueWith( - t => Plugin.StaticLogger.LogWarning(t.Exception), - TaskContinuationOptions.OnlyOnFaulted); - } - else - { - Plugin.StaticLogger.LogWarning($"Unable to store record of {key} for player {playername} - not considered a valid category."); - } - } - } - /// - /// Get the value stored under at . - /// - /// The RecordCategories category the value is stored under - /// The name of the player - /// This will return 0 if there is no record found for that player. It will return -1 if the category is invalid. - public int Retrieve(string key, string playername) - { - if (!Plugin.StaticConfig.CollectStatsEnabled) - { - return -1; - } - if (Array.IndexOf(RecordCategories.All, key) >= 0) - { - foreach (Record r in recordCache) - { - if (r.Category.Equals(key)) - { - foreach (RecordValue v in r.Values) - { - if (v.Key.Equals(playername)) - { - return v.Value; - } - } - } - } - } - Plugin.StaticLogger.LogWarning($"No stored record for player {playername} under {key}, returning default of 0."); - return 0; - } - - /// - /// Retrieve all stored values under . - /// - /// RecordCategories category to retrieve stored values from - /// A list of (playername, value) tuples. The list will have length 0 if there are no stored records. - public List> RetrieveAll(string key) - { - List> results = new List>(); - if (!Plugin.StaticConfig.CollectStatsEnabled) - { - return results; - } - - if (Array.IndexOf(RecordCategories.All, key) >= 0) - { - foreach (Record r in recordCache) - { - if (r.Category.Equals(key)) - { - foreach (RecordValue v in r.Values) - { - results.Add(Tuple.Create( - v.Key, v.Value - )); - } - } - } - } - - return results; - } - - /// - /// Retrieve the highest stored value under . - /// - /// RecordCategories category to retrieve stored values from - /// A single (playername, value) tuple. - public Tuple RetrieveHighest(string key) - { - if (!Plugin.StaticConfig.CollectStatsEnabled) - { - return Tuple.Create("not allowed", -1); - } - - if (Array.IndexOf(RecordCategories.All, key) >= 0) - { - string player = "no result"; - int records = -1; - foreach (Record r in recordCache) - { - if (r.Category.Equals(key)) - { - foreach (RecordValue v in r.Values) - { - if (v.Value > records) - { - player = v.Key; - records = v.Value; - } - } - } - } - return Tuple.Create(player, records); - } - else - { - return Tuple.Create($"not recording for {key}", -1); - } - } - - /// - /// Retrieve the lowest stored value under . - /// - /// RecordCategories category to retrieve stored values from - /// A single (playername, value) tuple. - public Tuple RetrieveLowest(string key) - { - if (!Plugin.StaticConfig.CollectStatsEnabled) - { - return Tuple.Create("not allowed", -1); - } + movepath = Path.Combine(basePath, MIGRATED_FILENAME); - if (Array.IndexOf(RecordCategories.All, key) >= 0) - { - string player = "no result"; - int records = int.MaxValue; - foreach (Record r in recordCache) - { - if (r.Category.Equals(key)) - { - foreach (RecordValue v in r.Values) - { - if (v.Value < records) - { - player = v.Key; - records = v.Value; - } - } - if (r.Values.Count == 0) - { - records = -1; - } - } - } - return Tuple.Create(player, records); - } - else - { - return Tuple.Create($"not recording for {key}", -1); - } + MigrateRecords(); } - /// - /// (Asynchronous) Writes the in-memory cache of records to disk. - /// - private async Task FlushCache() + private void MigrateRecords() { - if (!saveEnabled) - { - Plugin.StaticLogger.LogDebug("Saving records is disabled due to an error at load time."); - return; - } - if (Plugin.StaticConfig.CollectStatsEnabled) + //! check if storePath exists.. + if (System.IO.File.Exists(storepath)) { - string jsonString = JsonConvert.SerializeObject(recordCache); - - using (var stream = new StreamWriter(@storepath, false)) - { - await stream.WriteAsync(jsonString); - } - - Plugin.StaticLogger.LogDebug($"Flushed cached stats to {storepath}"); - } - } + Plugin.StaticLogger.LogInfo("Migrating from discovered Records.json to LiteDB"); + //! read all records in from storePath if they exist - /// - /// Builds the in-memory cache by reading from disk. - /// - private void PopulateCache() - { - if (File.Exists(storepath)) - { - string jsonString = File.ReadAllText(@storepath); try { + string jsonString = File.ReadAllText(@storepath); recordCache = JsonConvert.DeserializeObject>(jsonString); Plugin.StaticLogger.LogInfo($"Read existing stats from disk {storepath}"); } @@ -301,38 +65,40 @@ private void PopulateCache() { Plugin.StaticLogger.LogWarning($"No content found when reading {storepath} to read saved records. We will start with default values for all records."); Plugin.StaticLogger.LogDebug("File contained null and threw ArgumentNullException"); - InitializeEmptyCache(); + return; } catch (JsonException) { Plugin.StaticLogger.LogError($"Unable to parse the contents of {storepath} as JSON."); Plugin.StaticLogger.LogError("No records will be recorded to disk until existing file is moved, renamed, or deleted."); - saveEnabled = false; + return; + } + + //! store records into LiteDB + foreach (Record r in recordCache) + { + int count = 0; + foreach (RecordValue v in r.Values) + { + for (int i = 0; i < v.Value; i++) + { + Plugin.StaticDatabase.InsertSimpleStatRecord(r.Category, v.Key, 1); + count++; + } + + } + Plugin.StaticLogger.LogInfo($"Migrated {count} {r.Category} records"); + } + + //! move storePath to a new path with MIGRATED_FILENAME + System.IO.File.Move(storepath, movepath); + Plugin.StaticLogger.LogInfo($"Moved existing records.json to {MIGRATED_FILENAME}"); } else { - Plugin.StaticLogger.LogInfo($"Unable to find existing stats data at {storepath}. Creating new {DEFAULT_FILENAME}"); - InitializeEmptyCache(); - } - } - - private void InitializeEmptyCache() - { - recordCache = new List(); - foreach (string category in RecordCategories.All) - { - recordCache.Add(new Record - { - Category = category, - Values = new List() - }); + Plugin.StaticLogger.LogDebug("No records.json found, not migrating."); } - FlushCache().ContinueWith( - t => Plugin.StaticLogger.LogWarning(t.Exception), - TaskContinuationOptions.OnlyOnFaulted); } - - public Comparison> HighToLowSort = (x, y) => y.Item2.CompareTo(x.Item2); } } diff --git a/src/Records/Classes/CountResult.cs b/src/Records/Classes/CountResult.cs new file mode 100644 index 0000000..eb93369 --- /dev/null +++ b/src/Records/Classes/CountResult.cs @@ -0,0 +1,29 @@ + +using LiteDB; + +namespace DiscordConnector.Records +{ + public class CountResult + { + public string Name { get; } + public int Count { get; } + + [BsonCtor] + public CountResult(string name, int count) + { + + Name = name; + Count = count; + } + + public static int CompareByCount(CountResult cr1, CountResult cr2) + { + return cr1.Count.CompareTo(cr2.Count); + } + + public static int CompareByName(CountResult cr1, CountResult cr2) + { + return cr1.Name.CompareTo(cr2.Name); + } + } +} diff --git a/src/Records/Classes/SimpleStat.cs b/src/Records/Classes/SimpleStat.cs new file mode 100644 index 0000000..5a081a1 --- /dev/null +++ b/src/Records/Classes/SimpleStat.cs @@ -0,0 +1,67 @@ + +using LiteDB; + +namespace DiscordConnector.Records +{ + public class Position + { + public float x { get; } + public float y { get; } + public float z { get; } + + public Position() + { + x = 0; + y = 0; + z = 0; + } + public Position(float _x, float _y, float _z) + { + x = _x; + y = _y; + z = _z; + } + + public override string ToString() + { + return $"({x},{y},{z})"; + } + } + public class SimpleStat + { + public ObjectId StatId { get; } + public string Name { get; } + public System.DateTime Date { get; } + public ulong SteamId { get; } + public Position Pos { get; } + + public SimpleStat(string name, ulong steamId) + { + StatId = ObjectId.NewObjectId(); + Name = name; + SteamId = steamId; + Date = System.DateTime.Now; + Pos = new Position(); + } + + public SimpleStat(string name, ulong steamId, float x, float y, float z) + { + StatId = ObjectId.NewObjectId(); + Name = name; + SteamId = steamId; + Date = System.DateTime.Now; + Pos = new Position(x, y, z); + } + + [BsonCtor] + public SimpleStat(ObjectId _id, string name, System.DateTime date, ulong steamId, Position pos) + { + StatId = _id; + Name = name; + Date = date; + SteamId = steamId; + Pos = pos; + } + } + +} diff --git a/src/Records/Database.cs b/src/Records/Database.cs new file mode 100644 index 0000000..40a7e94 --- /dev/null +++ b/src/Records/Database.cs @@ -0,0 +1,227 @@ + +using System.Collections.Generic; +using LiteDB; +using UnityEngine; + +namespace DiscordConnector.Records +{ + internal class Database + { + private const string DB_NAME = "records.db"; + private static string DbPath; + private LiteDatabase db; + private ILiteCollection DeathCollection; + private ILiteCollection JoinCollection; + private ILiteCollection LeaveCollection; + private ILiteCollection ShoutCollection; + private ILiteCollection PingCollection; + + public Database(string rootStorePath) + { + DbPath = System.IO.Path.Combine(rootStorePath, DB_NAME); + Initialize(); + } + + public void Initialize() + { + db = new LiteDatabase(DbPath); + Plugin.StaticLogger.LogDebug($"LiteDB Connection Established to {DbPath}"); + DeathCollection = db.GetCollection("deaths"); + JoinCollection = db.GetCollection("joins"); + LeaveCollection = db.GetCollection("leaves"); + ShoutCollection = db.GetCollection("shouts"); + PingCollection = db.GetCollection("pings"); + } + + public void Dispose() + { + Plugin.StaticLogger.LogDebug("Closing LiteDB connection"); + db.Dispose(); + } + + private void InsertSimpleStatRecord(ILiteCollection collection, string playerName, ulong steamId, Vector3 pos) + { + var newRecord = new SimpleStat( + playerName, + steamId, + pos.x, pos.y, pos.z + ); + collection.Insert(newRecord); + + collection.EnsureIndex(x => x.Name); + collection.EnsureIndex(x => x.SteamId); + } + private void InsertSimpleStatRecord(ILiteCollection collection, string playerName, ulong steamId) + { + InsertSimpleStatRecord(collection, playerName, steamId, Vector3.zero); + } + + private int CountOfRecordsByName(ILiteCollection collection, string playerName) + { + return DeathCollection.Query() + .Where(x => x.Name.Equals(playerName)) + .Count(); + } + + private int CountOfRecordsBySteamId(ILiteCollection collection, ulong steamId) + { + return DeathCollection.Query() + .Where(x => x.SteamId == steamId) + .Count(); + } + + private List CountAllRecordsGroupBySteamId(ILiteCollection collection) + { + return ConvertBsonDocumentCountToDotNet( + collection.Query() + .GroupBy("SteamId") + .Select("{Name: @Key, Count: COUNT(*)}") + .ToList() + ); + } + + private List RetrieveAllRecordsGroupByName(ILiteCollection collection) + { + return ConvertBsonDocumentCountToDotNet( + collection.Query() + .GroupBy("Name") + .Select("{Name: @Key, Count: COUNT(*)}") + .ToList() + ); + } + + private List ConvertBsonDocumentCountToDotNet(List bsonDocuments) + { + List results = new List(); + foreach (BsonDocument doc in bsonDocuments) + { + if (doc.ContainsKey("Name") && doc.ContainsKey("Count")) + { + results.Add(new CountResult( + doc["Name"].AsString, + doc["Count"].AsInt32 + )); + } + } + return results; + } + + public List RetrieveAllRecordsGroupByName(string key) + { + switch (key) + { + case Categories.Death: + return RetrieveAllRecordsGroupByName(DeathCollection); + case Categories.Join: + return RetrieveAllRecordsGroupByName(JoinCollection); + case Categories.Leave: + return RetrieveAllRecordsGroupByName(LeaveCollection); + case Categories.Ping: + return RetrieveAllRecordsGroupByName(PingCollection); + case Categories.Shout: + return RetrieveAllRecordsGroupByName(ShoutCollection); + default: + Plugin.StaticLogger.LogDebug($"RetrieveAllRecordsGroupByName, invalid key '{key}'"); + return new List(); + } + } + public List CountAllRecordsGroupBySteamId(string key) + { + switch (key) + { + case Categories.Death: + return CountAllRecordsGroupBySteamId(DeathCollection); + case Categories.Join: + return CountAllRecordsGroupBySteamId(JoinCollection); + case Categories.Leave: + return CountAllRecordsGroupBySteamId(LeaveCollection); + case Categories.Ping: + return CountAllRecordsGroupBySteamId(PingCollection); + case Categories.Shout: + return CountAllRecordsGroupBySteamId(ShoutCollection); + default: + Plugin.StaticLogger.LogDebug($"CountAllRecordsGroupBySteamId, invalid key '{key}'"); + return new List(); + } + } + + public int CountOfRecordsByName(string key, string playerName) + { + if (!Plugin.StaticConfig.CollectStatsEnabled) + { + return -1; + } + switch (key) + { + case Categories.Death: + return CountOfRecordsByName(DeathCollection, playerName); + case Categories.Join: + return CountOfRecordsByName(JoinCollection, playerName); + case Categories.Leave: + return CountOfRecordsByName(LeaveCollection, playerName); + case Categories.Ping: + return CountOfRecordsByName(PingCollection, playerName); + case Categories.Shout: + return CountOfRecordsByName(ShoutCollection, playerName); + default: + Plugin.StaticLogger.LogDebug($"CountOfRecordsBySteamId, invalid key '{key}'"); + return -2; + } + } + + public int CountOfRecordsBySteamId(string key, ulong steamId) + { + if (!Plugin.StaticConfig.CollectStatsEnabled) + { + return -1; + } + switch (key) + { + case Categories.Death: + return CountOfRecordsBySteamId(DeathCollection, steamId); + case Categories.Join: + return CountOfRecordsBySteamId(JoinCollection, steamId); + case Categories.Leave: + return CountOfRecordsBySteamId(LeaveCollection, steamId); + case Categories.Ping: + return CountOfRecordsBySteamId(PingCollection, steamId); + case Categories.Shout: + return CountOfRecordsBySteamId(ShoutCollection, steamId); + default: + Plugin.StaticLogger.LogDebug($"CountOfRecordsBySteamId, invalid key '{key}'"); + return -2; + } + } + + public void InsertSimpleStatRecord(string key, string playerName, ulong steamId, Vector3 pos) + { + + switch (key) + { + case Categories.Death: + InsertSimpleStatRecord(DeathCollection, playerName, steamId, pos); + break; + case Categories.Join: + InsertSimpleStatRecord(JoinCollection, playerName, steamId, pos); + break; + case Categories.Leave: + InsertSimpleStatRecord(LeaveCollection, playerName, steamId, pos); + break; + case Categories.Ping: + InsertSimpleStatRecord(PingCollection, playerName, steamId, pos); + break; + case Categories.Shout: + InsertSimpleStatRecord(ShoutCollection, playerName, steamId, pos); + break; + default: + Plugin.StaticLogger.LogDebug($"InsertSimpleStatRecord, invalid key '{key}'"); + break; + } + } + public void InsertSimpleStatRecord(string key, string playerName, ulong steamId) + { + InsertSimpleStatRecord(key, playerName, steamId, Vector3.zero); + } + + } +} diff --git a/src/Records/RecordsHelper.cs b/src/Records/RecordsHelper.cs new file mode 100644 index 0000000..1761ffa --- /dev/null +++ b/src/Records/RecordsHelper.cs @@ -0,0 +1,90 @@ +using System; +using System.Collections.Generic; +using UnityEngine; + +namespace DiscordConnector.Records +{ + /// + /// These are categories used when keeping track of values in the record system. It is a simple system + /// that currently only supports storing string:integer pairings underneath one of these categories. + /// + public static class Categories + { + public const string Death = "death"; + public const string Join = "join"; + public const string Leave = "leave"; + public const string Ping = "ping"; + public const string Shout = "shout"; + + public readonly static string[] All = new string[] { + Death, + Join, + Leave, + Ping, + Shout + }; + } + + public static class Helper + { + public static List TopNResultForCategory(string key, int n) + { + List queryResults = Plugin.StaticDatabase.RetrieveAllRecordsGroupByName(key); + Plugin.StaticLogger.LogDebug($"TopNResultForCategory {key} n={n}, results={queryResults.Count}"); + if (queryResults.Count == 0) + { + return queryResults; + } + + queryResults.Sort(CountResult.CompareByCount); // sorts lowest to highest + queryResults.Reverse(); // Now high --> low + + if (queryResults.Count <= n) + { + return queryResults; + } + + return queryResults.GetRange(0, n); + } + + public static CountResult TopResultForCategory(string key) + { + var results = Helper.TopNResultForCategory(key, 1); + if (results.Count == 0) + { + return new CountResult("", 0); + } + return results[0]; + } + + public static List BottomNResultForCategory(string key, int n) + { + + List queryResults = Plugin.StaticDatabase.RetrieveAllRecordsGroupByName(key); + Plugin.StaticLogger.LogDebug($"BottomNResultForCategory {key} n={n}, results={queryResults.Count}"); + if (queryResults.Count == 0) + { + return queryResults; + } + + queryResults.Sort(CountResult.CompareByCount); // sorts lowest to highest + + if (queryResults.Count <= n) + { + return queryResults; + } + + return queryResults.GetRange(0, n); + } + + public static CountResult BottomResultForCategory(string key) + { + var results = Helper.BottomNResultForCategory(key, 1); + if (results.Count == 0) + { + return new CountResult("", 0); + } + return results[0]; + } + } +} From c8fd95549f80b27abd8bca9378d5e30e82acdf28 Mon Sep 17 00:00:00 2001 From: Nicholas Westerhausen <2317381+nwesterhausen@users.noreply.github.com> Date: Thu, 28 Oct 2021 09:01:38 -0400 Subject: [PATCH 13/15] :bookmark: bump version 1.5.0 --- Metadata/CHANGELOG.md | 47 ++++++++++++++++++ Metadata/DiscordConnector-Nexus.readme | 2 +- Metadata/README.md | 69 ++++++++++++-------------- Metadata/manifest.json | 2 +- docs/changelog.md | 47 ++++++++++++++++++ src/PluginInfo.cs | 2 +- 6 files changed, 128 insertions(+), 41 deletions(-) diff --git a/Metadata/CHANGELOG.md b/Metadata/CHANGELOG.md index f8e9eee..2fecaea 100644 --- a/Metadata/CHANGELOG.md +++ b/Metadata/CHANGELOG.md @@ -2,6 +2,53 @@ A full changelog for reference. +### Version 1.5.0 + +Features: + +- Using LiteDB for record storage. + +Because of how unreliable storing the records in a "roll-your-own" +database with a JSON file was, and because of the increased flexibility +in what could be stored, I've changed the storage system for the +recorded player stats to use LiteDB. Currently this means records for +join/leave/death/shout/ping will be timestamped, include the position of +the event, have the player name, and the player's steamid. Hopefully +adding this additional information will allow for more customization +options for the users of this mod. + +It is set up to do a migration on first load of the updated plugin, the +steps it follows for that is: + + 1. check if records.json (or configured name) exists + 2. read all records from the file + 3. parse the records + 4. loop through all the records and add them to the database + + Records added this way will have position of zero and a + steamid of 1. + + 5. move the records.json file to records.json.migrated + +If you don't want to have it auto-migrate the records, rename your +records.json or delete it. If the name does not match exactly it will +not migrate the data. + +For the migration steps, it will be outputting log information (at INFO +level) with how many records were migrated and which steps completed. + +- Ranked Lowest Player Leaderbaord + +Added an inverse of the Top Player leaderboard. + +- Custom leaderboard heading messages + +Added configuration for the messages sent at the top of the leaderboard +messages. + +- The variable `%PUBLICIP%` can be used in _any_ message configuration + now. + ### Version 1.4.4 Fixes: diff --git a/Metadata/DiscordConnector-Nexus.readme b/Metadata/DiscordConnector-Nexus.readme index fac8ee7..b43b0eb 100644 --- a/Metadata/DiscordConnector-Nexus.readme +++ b/Metadata/DiscordConnector-Nexus.readme @@ -1,4 +1,4 @@ -DISCORD CONNECTOR - 1.4.0 +DISCORD CONNECTOR - 1.5.0 Connect your Valheim server to a Discord Webhook. Works for both dedicated and client-hosted servers. (Visit the github repo for a how-to guide.) :: REQUIREMENTS :: diff --git a/Metadata/README.md b/Metadata/README.md index 0eab14e..f6d4def 100644 --- a/Metadata/README.md +++ b/Metadata/README.md @@ -52,59 +52,52 @@ records.json 1.2.0+ (PlayerName changed to Key) [{"Category":"death","Values":[{"Key":"Xithyr","Value":13} ... ``` -### Version 1.4.4 - -Fixes: - -- Position being sent with event messages even if event position was disabled in config - -### Version 1.4.3 - -Fixes: - -- Event messages were sending the wrong message (start instead of end and vice-versa) -- Event Stop messages were sending zero coordinates -- If you had enabled first death message and death message (this is default settings), you would -get two messages. This has been changed to merge the messages into one if both settings are on -and it's a player's first death. +### Version 1.5.0 Features: -- Added toggles to enable/disable some event debug messages (all disabled by default) -- Added a toggle to enable/disable a debug message with responses from the webhook (disabled by default) - -### Version 1.4.2 +- Using LiteDB for record storage. -Fixes: +Because of how unreliable storing the records in a "roll-your-own" +database with a JSON file was, and because of the increased flexibility +in what could be stored, I've changed the storage system for the +recorded player stats to use LiteDB. Currently this means records for +join/leave/death/shout/ping will be timestamped, include the position of +the event, have the player name, and the player's steamid. Hopefully +adding this additional information will allow for more customization +options for the users of this mod. -- Least deaths leaderboard wasn't respecting the correct config entry. (THanks @thedefside) +It is set up to do a migration on first load of the updated plugin, the +steps it follows for that is: -### Version 1.4.1 + 1. check if records.json (or configured name) exists + 2. read all records from the file + 3. parse the records + 4. loop through all the records and add them to the database -Fixes: + Records added this way will have position of zero and a + steamid of 1. -- Removed the two debug logging calls for events -- sorry for the log spam! + 5. move the records.json file to records.json.migrated -### Version 1.4.0 +If you don't want to have it auto-migrate the records, rename your +records.json or delete it. If the name does not match exactly it will +not migrate the data. -Features: +For the migration steps, it will be outputting log information (at INFO +level) with how many records were migrated and which steps completed. -- 10 user defined variables that can be used an any messages (%VAR1% thru %VAR10%). These are set in their own configuration file, -`games.nwest.valheim.discordconnector-variables.cfg` which will get generated first time 1.4.0 is run. -- The position of where the player/ping/event coordinates are inserted into messages is configurable using the `%POS%` variable in -the messages config. It won't be replaced if the "send coordinates" toggle is off for that message. If you don't include a `%POS%` -variable, it will append the coordinates as happens with previous versions. +- Ranked Lowest Player Leaderbaord -Fixes: +Added an inverse of the Top Player leaderboard. -- Fixed an off-by-one error in the Top Players leaderboard (the default leaderboard) (Thanks @thedefside) -- Fixed configuration not referencing proper settings (Thanks @thedefside) -- Fixed event messages (now properly functioning on dedicated servers) +- Custom leaderboard heading messages -Breaking Changes: +Added configuration for the messages sent at the top of the leaderboard +messages. -- If you used `%PLAYERS%` in any of the event messages, you need to remove it. With the changes required for the event messages -functionality, it is not supportable at this time. +- The variable `%PUBLICIP%` can be used in _any_ message configuration + now. Full changelog history available on the [Github repository](https://github.com/nwesterhausen/valheim-discordconnector/blob/main/Metadata/CHANGELOG.md) diff --git a/Metadata/manifest.json b/Metadata/manifest.json index c247b6f..a3c46a8 100644 --- a/Metadata/manifest.json +++ b/Metadata/manifest.json @@ -1,6 +1,6 @@ { "name": "DiscordConnector", - "version_number": "1.4.4", + "version_number": "1.5.0", "website_url": "https://discordconnector.valheim.nwest.games/", "description": "Connects your Valheim server to a Discord webhook. Works for both dedicated and client-hosted servers.", "dependencies": ["denikson-BepInExPack_Valheim-5.4.1600"] diff --git a/docs/changelog.md b/docs/changelog.md index 85eddde..e9b0381 100644 --- a/docs/changelog.md +++ b/docs/changelog.md @@ -2,6 +2,53 @@ A full changelog +### Version 1.5.0 + +Features: + +- Using LiteDB for record storage. + +Because of how unreliable storing the records in a "roll-your-own" +database with a JSON file was, and because of the increased flexibility +in what could be stored, I've changed the storage system for the +recorded player stats to use LiteDB. Currently this means records for +join/leave/death/shout/ping will be timestamped, include the position of +the event, have the player name, and the player's steamid. Hopefully +adding this additional information will allow for more customization +options for the users of this mod. + +It is set up to do a migration on first load of the updated plugin, the +steps it follows for that is: + + 1. check if records.json (or configured name) exists + 2. read all records from the file + 3. parse the records + 4. loop through all the records and add them to the database + + Records added this way will have position of zero and a + steamid of 1. + + 5. move the records.json file to records.json.migrated + +If you don't want to have it auto-migrate the records, rename your +records.json or delete it. If the name does not match exactly it will +not migrate the data. + +For the migration steps, it will be outputting log information (at INFO +level) with how many records were migrated and which steps completed. + +- Ranked Lowest Player Leaderbaord + +Added an inverse of the Top Player leaderboard. + +- Custom leaderboard heading messages + +Added configuration for the messages sent at the top of the leaderboard +messages. + +- The variable `%PUBLICIP%` can be used in _any_ message configuration + now. + ### Version 1.4.4 Fixes: diff --git a/src/PluginInfo.cs b/src/PluginInfo.cs index c5e709c..740fd6f 100644 --- a/src/PluginInfo.cs +++ b/src/PluginInfo.cs @@ -17,7 +17,7 @@ internal static class PluginInfo { public const string PLUGIN_ID = "games.nwest.valheim.discordconnector"; public const string PLUGIN_NAME = "Valheim Discord Connector"; - public const string PLUGIN_VERSION = "1.4.4"; + public const string PLUGIN_VERSION = "1.5.0"; public const string PLUGIN_REPO_SHORT = "github: nwesterhausen/valheim-discordconnector"; public const string PLUGIN_AUTHOR = "Nicholas Westerhausen"; } From a37057a4a768a248c41b4dfb3843d1d1f8b5fae0 Mon Sep 17 00:00:00 2001 From: Nicholas Westerhausen <2317381+nwesterhausen@users.noreply.github.com> Date: Thu, 28 Oct 2021 09:04:40 -0400 Subject: [PATCH 14/15] :memo: update readme --- README.md | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index ff8fd4b..5c4d84f 100644 --- a/README.md +++ b/README.md @@ -30,8 +30,10 @@ The compiled plugin will be in a zip ready for upload at `bin/DiscordConnector.z ### Dependencies -For JSON serialization, I chose to use the System.Text.Json library which is part of -the most recent .NET but can be used with .NET 4.8 which is used in this project. +For JSON serialization, using Newtonsoft.Json + +For data storage/retrieval using [LiteDB](https://www.litedb.org/) +(If you want to read the database file generated, you can use [LitDB Studio](https://github.com/mbdavid/LiteDB.Studio/releases/latest)) ### Contributors From da30a712f5b7a507fb0dcdb4da9b4fe6476b1c7d Mon Sep 17 00:00:00 2001 From: Nicholas Westerhausen <2317381+nwesterhausen@users.noreply.github.com> Date: Thu, 28 Oct 2021 11:05:44 -0400 Subject: [PATCH 15/15] :bug: prevent shouts by a null peer from causing an error --- src/Patches/ChatPatches.cs | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/Patches/ChatPatches.cs b/src/Patches/ChatPatches.cs index b5688b2..526c9e6 100644 --- a/src/Patches/ChatPatches.cs +++ b/src/Patches/ChatPatches.cs @@ -16,8 +16,12 @@ private static void Prefix(ref GameObject go, ref long senderID, ref Vector3 pos Plugin.StaticLogger.LogInfo($"Ignored shout from user on muted list. User: {user} Shout: {text}. Index {Plugin.StaticConfig.MutedPlayers.IndexOf(user)}"); return; } - - ulong peerSteamID = ((ZSteamSocket)ZNet.instance.GetPeerByPlayerName(user).m_socket).GetPeerID().m_SteamID; // Get the SteamID from peer. + ulong peerSteamID = 0; + ZNetPeer peerInstance = ZNet.instance.GetPeerByPlayerName(user); + if (peerInstance != null) + { + peerSteamID = ((ZSteamSocket)peerInstance.m_socket).GetPeerID().m_SteamID; // Get the SteamID from peer. + } switch (type) { case Talker.Type.Ping: