npc counts as players killed for space event
Space event npc count as kills
if (killed == null killed.IsNpc) return; => if (killed == null !killed.userID.IsSteamId()) return
This is what i told a fix could be havent tried just yet however
use this for fixing some NPCS couting as pvp kills
using System;
using System.Collections.Generic;
using Oxide.Core;
using Oxide.Core.Libraries.Covalence;
using Oxide.Core.Configuration;
using Oxide.Core.Plugins;
using Oxide.Game.Rust.Cui;
using Newtonsoft.Json;
using UnityEngine;
using System.Linq;
using System.Globalization;
namespace Oxide.Plugins
{
[Info("Player Challenges", "k1lly0u", "2.0.52")]
[Description("Keep track of various statistics and set titles to players when certain criteria have been met or when they are a leader of a challenge category")]
class PlayerChallenges : RustPlugin
{
#region Fields
[PluginReference] Plugin BetterChat;
[PluginReference] Plugin EventManager;
[PluginReference] Plugin LustyMap;
[PluginReference] Plugin Clans;
[PluginReference] Plugin Friends;
ChallengeData chData;
private DynamicConfigFile data;
private Dictionary<ulong, StatData> statCache = new Dictionary<ulong, StatData>();
private Dictionary<Challenges, LeaderData> titleCache = new Dictionary<Challenges, LeaderData>();
private Dictionary<ulong, WoundedData> woundedData = new Dictionary<ulong, WoundedData>();
private Hash<ulong, Hash<ulong, float>> damageData = new Hash<ulong, Hash<ulong, float>>();
private static Dictionary<string, string> uiColors = new Dictionary<string, string>();
private bool UIDisabled = false;
#endregion
#region UI Creation
public static class UI
{
public static CuiElementContainer Container(string panelName, string aMin, string aMax, bool cursor = false)
{
CuiElementContainer container = new CuiElementContainer()
{
{
new CuiPanel
{
Image = {Color = uiColors["background"]},
RectTransform = {AnchorMin = aMin, AnchorMax = aMax},
CursorEnabled = cursor
},
new CuiElement().Parent = "Overlay",
panelName
}
};
return container;
}
public static void Panel(ref CuiElementContainer container, string panel, string aMin, string aMax, bool cursor = false)
{
container.Add(new CuiPanel
{
Image = { Color = uiColors["panel"] },
RectTransform = { AnchorMin = aMin, AnchorMax = aMax },
CursorEnabled = cursor
},
panel);
}
public static void Label(ref CuiElementContainer container, string panel, string text, int size, string aMin, string aMax, TextAnchor align = TextAnchor.MiddleCenter, float fadein = 0f)
{
container.Add(new CuiLabel
{
Text = { FontSize = size, Align = align, FadeIn = fadein, Text = text },
RectTransform = { AnchorMin = aMin, AnchorMax = aMax }
},
panel);
}
public static void Button(ref CuiElementContainer container, string panel, string text, int size, string aMin, string aMax, string command, TextAnchor align = TextAnchor.MiddleCenter, float fadein = 0f)
{
container.Add(new CuiButton
{
Button = { Color = uiColors["button"], Command = command, FadeIn = fadein },
RectTransform = { AnchorMin = aMin, AnchorMax = aMax },
Text = { Text = text, FontSize = size, Align = align }
},
panel);
}
public static string Color(string hexColor, float alpha)
{
if (hexColor.StartsWith("#"))
hexColor = hexColor.Substring(1);
int red = int.Parse(hexColor.Substring(0, 2), NumberStyles.AllowHexSpecifier);
int green = int.Parse(hexColor.Substring(2, 2), NumberStyles.AllowHexSpecifier);
int blue = int.Parse(hexColor.Substring(4, 2), NumberStyles.AllowHexSpecifier);
return $"{(double)red / 255} {(double)green / 255} {(double)blue / 255} {alpha}";
}
}
#endregion
#region UI Leaderboard
private string UIMain = "PCUI_Main";
private void CreateMenu(BasePlayer player)
{
CloseMap(player);
CuiHelper.DestroyUi(player, UIMain);
CreateMenuContents(player, 0);
}
private void CreateMenuContents(BasePlayer player, int page = 0)
{
CuiElementContainer container = UI.Container(UIMain, "0 0", "1 1", true);
UI.Panel(ref container, UIMain, "0.005 0.93", "0.995 0.99");
UI.Label(ref container, UIMain, $"<color={configData.Colors.TextColor1}>{MSG("UITitle").Replace("{Version}", Version.ToString())}</color>", 22, "0.05 0.93", "0.6 0.99", TextAnchor.MiddleLeft);
KeyValuePair<Challenges, ConfigData.ChallengeInfo>[] elements = configData.ChallengeSettings.Where(x => x.Value.Enabled).OrderByDescending(x => x.Value.UIPosition).Reverse().ToArray();
int count = page * 5;
int number = 0;
float dimension = 0.19f;
for (int i = count; i < count + 5; i++)
{
if (elements.Length < i + 1) continue;
float leftPos = 0.005f + (number * (dimension + 0.01f));
AddMenuStats(ref container, UIMain, elements[i].Key, leftPos, 0.01f, leftPos + dimension, 0.92f);
number++;
}
if (page > 0)
UI.Button(ref container, UIMain, "Previous", 16, "0.63 0.94", "0.73 0.98", $"PCUI_ChangePage {page - 1}");
if (elements.Length > count + 5)
UI.Button(ref container, UIMain, "Next", 16, "0.74 0.94", "0.84 0.98", $"PCUI_ChangePage {page + 1}");
UI.Button(ref container, UIMain, "Close", 16, "0.85 0.94", "0.95 0.98", "PCUI_DestroyAll");
CuiHelper.AddUi(player, container);
}
private void AddMenuStats(ref CuiElementContainer MenuElement, string panel, Challenges type, float left, float bottom, float right, float top)
{
if (configData.ChallengeSettings[type].Enabled)
{
UI.Panel(ref MenuElement, UIMain, $"{left} {bottom}", $"{right} {top}");
UI.Label(ref MenuElement, UIMain, GetLeaders(type), 16, $"{left + 0.005f} {bottom + 0.01f}", $"{right - 0.005f} {top - 0.01f}", TextAnchor.UpperLeft);
}
}
#region UI Commands
[ConsoleCommand("PCUI_ChangePage")]
private void cmdChangePage(ConsoleSystem.Arg arg)
{
BasePlayer player = arg.Connection.player as BasePlayer;
if (player == null)
return;
CuiHelper.DestroyUi(player, UIMain);
CreateMenuContents(player, arg.GetInt(0));
}
[ConsoleCommand("PCUI_DestroyAll")]
private void cmdDestroyAll(ConsoleSystem.Arg arg)
{
BasePlayer player = arg.Connection.player as BasePlayer;
if (player == null)
return;
DestroyUI(player);
OpenMap(player);
}
#endregion
#region UI Functions
private string GetLeaders(Challenges type)
{
string listNames = $" -- <color={configData.Colors.TextColor1}>{MSG(type.ToString()).ToUpper()}</color>\n\n";
List<KeyValuePair<string, int>> userStats = new List<KeyValuePair<string, int>>();
foreach (KeyValuePair<ulong, StatData> entry in statCache)
{
string name = entry.Value.DisplayName;
userStats.Add(new KeyValuePair<string, int>(name, entry.Value.Stats[type]));
}
IEnumerable<KeyValuePair<string, int>> leaders = userStats.OrderByDescending(a => a.Value).Take(25);
int i = 1;
foreach (KeyValuePair<string, int> entry in leaders)
{
listNames += $"{i}. - <color={configData.Colors.TextColor1}>{entry.Value}</color> - {entry.Key}\n";
i++;
}
return listNames;
}
private object GetTypeFromString(string name)
{
foreach (object type in Enum.GetValues(typeof(Challenges)))
{
if (type.ToString() == name)
return type;
}
return null;
}
private void DestroyUI(BasePlayer player)
{
CuiHelper.DestroyUi(player, UIMain);
}
#endregion
#endregion
#region External Calls
private void CloseMap(BasePlayer player)
{
if (LustyMap && LustyMap.IsLoaded)
{
LustyMap.Call("DisableMaps", player);
}
}
private void OpenMap(BasePlayer player)
{
if (LustyMap && LustyMap.IsLoaded)
{
LustyMap.Call("EnableMaps", player);
}
}
private bool IsPlaying(BasePlayer player)
{
if (EventManager && EventManager.IsLoaded)
{
object isPlaying = EventManager.Call("isPlaying", player);
if (isPlaying is bool && (bool)isPlaying)
return true;
}
return false;
}
private bool IsClanmate(ulong playerId, ulong friendId)
{
if (!Clans || !Clans.IsLoaded) return false;
object playerTag = Clans?.Call("GetClanOf", playerId);
object friendTag = Clans?.Call("GetClanOf", friendId);
if (playerTag is string && friendTag is string)
if (playerTag == friendTag) return true;
return false;
}
private bool IsFriend(ulong playerId, ulong friendId)
{
if (!Friends || !Friends.IsLoaded) return false;
object isFriend = Friends?.Call("IsFriend", playerId, friendId);
if (isFriend is bool && (bool)isFriend)
return true;
return false;
}
#endregion
#region Oxide Hooks
private void Loaded()
{
data = Interface.Oxide.DataFileSystem.GetFile("challenge_data");
lang.RegisterMessages(Messages, this);
}
private void OnServerInitialized()
{
LoadData();
CheckValidData();
RegisterTitles();
RegisterGroups();
AddAllUsergroups();
configData.ChallengeSettings[Challenges.NPCKills].Enabled = configData.Options.NPCKillSeperate;
uiColors = new Dictionary<string, string>()
{
["background"] = UI.Color(configData.Colors.Background.Color, configData.Colors.Background.Alpha),
["button"] = UI.Color(configData.Colors.Button.Color, configData.Colors.Button.Alpha),
["panel"] = UI.Color(configData.Colors.Panel.Color, configData.Colors.Panel.Alpha),
};
if (!configData.ChallengeSettings[Challenges.RocketsFired].Enabled)
Unsubscribe(nameof(OnRocketLaunched));
if (!configData.ChallengeSettings[Challenges.PlayersHealed].Enabled)
Unsubscribe(nameof(OnHealingItemUse));
if (!configData.ChallengeSettings[Challenges.PlantsGathered].Enabled)
{
Unsubscribe(nameof(OnGrowableGather));
Unsubscribe(nameof(OnCollectiblePickup));
}
if (!configData.ChallengeSettings[Challenges.StructuresBuilt].Enabled)
Unsubscribe(nameof(OnEntityBuilt));
if (!configData.ChallengeSettings[Challenges.ExplosivesThrown].Enabled)
Unsubscribe(nameof(OnExplosiveThrown));
if (!configData.ChallengeSettings[Challenges.StructuresRepaired].Enabled)
Unsubscribe(nameof(OnStructureRepair));
if (configData.Options.UseUpdateTimer)
CheckUpdateTimer();
foreach (BasePlayer player in BasePlayer.activePlayerList)
OnPlayerConnected(player);
}
private void Unload()
{
SaveData();
foreach (BasePlayer player in BasePlayer.activePlayerList)
DestroyUI(player);
RemoveAllUsergroups();
uiColors = null;
}
private void OnPluginLoaded(Plugin plugin)
{
if (plugin?.Title == "BetterChat")
RegisterTitles();
}
private void OnServerSave() => SaveData();
private void OnPlayerConnected(BasePlayer player)
{
if (player == null)
return;
if (statCache.ContainsKey(player.userID))
{
if (statCache[player.userID].DisplayName != player.displayName)
statCache[player.userID].DisplayName = player.displayName;
}
}
private void OnRocketLaunched(BasePlayer player, BaseEntity entity)
{
if (player == null || player is NPCPlayer)
return;
AddPoints(player, Challenges.RocketsFired, 1);
}
private void OnHealingItemUse(HeldEntity item, BasePlayer target)
{
BasePlayer player = item.GetOwnerPlayer();
if (player == null || player is NPCPlayer)
return;
if (player != target)
{
if (target.health == target.MaxHealth())
return;
AddPoints(player, Challenges.PlayersHealed, 1);
}
}
private void OnItemCraftFinished(ItemCraftTask task, Item item, ItemCrafter itemCrafter)
{
if (!itemCrafter)
return;
BasePlayer player = itemCrafter.owner;
if (!player || player is NPCPlayer)
return;
if (item.info.category == ItemCategory.Attire && configData.ChallengeSettings[Challenges.ClothesCrafted].Enabled)
AddPoints(player, Challenges.ClothesCrafted, 1);
if (item.info.category == ItemCategory.Weapon && configData.ChallengeSettings[Challenges.WeaponsCrafted].Enabled)
AddPoints(player, Challenges.WeaponsCrafted, 1);
}
private void OnGrowableGather(GrowableEntity growable, Item item, BasePlayer player)
{
if (player == null || player is NPCPlayer)
return;
AddPoints(player, Challenges.PlantsGathered, 1);
}
private void OnCollectiblePickup(CollectibleEntity collectible, BasePlayer player)
{
if (collectible == null)
return;
if (player == null || player is NPCPlayer)
return;
foreach (ItemAmount itemAmount in collectible.itemList)
{
if (plantShortnames.Contains(itemAmount?.itemDef?.shortname))
AddPoints(player, Challenges.PlantsGathered, 1);
}
}
private void OnDispenserGather(ResourceDispenser dispenser, BaseEntity entity, Item item)
{
BasePlayer player = entity.ToPlayer();
if (player == null || player is NPCPlayer || dispenser == null)
return;
if (dispenser.gatherType == ResourceDispenser.GatherType.Tree && configData.ChallengeSettings[Challenges.WoodGathered].Enabled)
AddPoints(player, Challenges.WoodGathered, item.amount);
if (dispenser.gatherType == ResourceDispenser.GatherType.Ore && configData.ChallengeSettings[Challenges.RocksGathered].Enabled)
AddPoints(player, Challenges.RocksGathered, item.amount);
}
private void OnEntityBuilt(Planner plan, GameObject go)
{
BasePlayer player = plan?.GetOwnerPlayer();
if (player == null || player is NPCPlayer)
return;
BaseEntity entity = go.ToBaseEntity();
if (entity == null)
return;
if ((entity is BuildingBlock) || (entity is SimpleBuildingBlock))
AddPoints(player, Challenges.StructuresBuilt, 1);
}
private void CanBeWounded(BasePlayer player, HitInfo hitInfo)
{
if (player == null || player is NPCPlayer || hitInfo == null) return;
BasePlayer attacker = hitInfo.InitiatorPlayer;
if (attacker != null)
{
if (attacker == player || IsPlaying(attacker) || IsFriend(attacker.userID, player.userID) || IsClanmate(attacker.userID, player.userID))
return;
woundedData[player.userID] = new WoundedData { distance = Vector3.Distance(player.transform.position, attacker.transform.position), attackerId = attacker.userID };
}
}
private void OnPlayerRecover(BasePlayer player)
{
if (player == null || player is NPCPlayer)
return;
if (woundedData.ContainsKey(player.userID))
woundedData.Remove(player.userID);
}
private void OnEntityTakeDamage(BaseCombatEntity entity, HitInfo info)
{
if (entity == null || info == null)
return;
if (entity is PatrolHelicopter || entity is BradleyAPC)
{
BasePlayer attacker = info.InitiatorPlayer;
if (attacker == null)
return;
Hash<ulong, float> entityAttackers;
if (!damageData.TryGetValue(entity.net.ID.Value, out entityAttackers))
entityAttackers = damageData[entity.net.ID.Value] = new Hash<ulong, float>();
entityAttackers[attacker.userID] += info.damageTypes.Total();
}
}
private void OnEntityDeath(BaseCombatEntity entity, HitInfo info)
{
if (entity == null || info == null)
return;
BasePlayer attacker = info.InitiatorPlayer;
if (attacker != null)
{
if (!attacker.IsNpc && attacker.userID.IsSteamId())
CheckEntry(attacker);
}
if (entity is BasePlayer)
{
BasePlayer victim = entity.ToPlayer();
if (attacker == null || attacker is NPCPlayer || victim == null || !victim.userID.IsSteamId())
return;
if (attacker == victim || IsPlaying(attacker) || IsFriend(attacker.userID, victim.userID) || IsClanmate(attacker.userID, victim.userID) || (configData.Options.IgnoreSleepers && victim.IsSleeping()))
return;
if (info.isHeadshot && configData.ChallengeSettings[Challenges.Headshots].Enabled && !attacker.IsNpc)
AddPoints(attacker, Challenges.Headshots, 1);
string weapon = info?.Weapon?.GetItem()?.info?.shortname;
if (!string.IsNullOrEmpty(weapon) && !attacker.IsNpc)
{
if (victim.IsNpc && configData.Options.NPCKillSeperate)
{
if (configData.ChallengeSettings[Challenges.NPCKills].Enabled)
AddPoints(attacker, Challenges.NPCKills, 1);
}
else
{
if (bladeShortnames.Contains(weapon) && configData.ChallengeSettings[Challenges.BladeKills].Enabled)
AddPoints(attacker, Challenges.BladeKills, 1);
else if (meleeShortnames.Contains(weapon) && configData.ChallengeSettings[Challenges.MeleeKills].Enabled)
AddPoints(attacker, Challenges.MeleeKills, 1);
else if ((weapon == "bow.hunting" || weapon == "bow.compound") && configData.ChallengeSettings[Challenges.ArrowKills].Enabled)
AddPoints(attacker, Challenges.ArrowKills, 1);
else if (weapon == "pistol.revolver" && configData.ChallengeSettings[Challenges.RevolverKills].Enabled)
AddPoints(attacker, Challenges.RevolverKills, 1);
else if (configData.ChallengeSettings[Challenges.PlayersKilled].Enabled)
AddPoints(attacker, Challenges.PlayersKilled, 1);
}
float distance = Vector3.Distance(attacker.transform.position, entity.transform.position);
WoundedData woundData;
if (woundedData.TryGetValue(victim.userID, out woundData))
{
if (attacker.userID == woundData.attackerId)
distance = woundData.distance;
woundedData.Remove(victim.userID);
}
if (!attacker.IsNpc)
AddDistance(attacker, victim.IsNpc && configData.Options.NPCPVEKills ? Challenges.PVEKillDistance : Challenges.PVPKillDistance, (int)distance);
}
}
else if (entity.GetComponent<BaseNpc>() != null)
{
if (attacker == null || attacker.IsNpc)
return;
float distance = Vector3.Distance(attacker.transform.position, entity.transform.position);
AddDistance(attacker, Challenges.PVEKillDistance, (int)distance);
AddPoints(attacker, Challenges.AnimalKills, 1);
}
else
{
Hash<ulong, float> entityAttackers;
if (damageData.TryGetValue(entity.net.ID.Value, out entityAttackers))
{
ulong mostDamage = entityAttackers.OrderByDescending(x => x.Value)?.First().Key ?? 0U;
if (mostDamage != 0U)
AddPoints(mostDamage, entity is PatrolHelicopter ? Challenges.HelicopterKills : Challenges.APCKills, 1);
damageData.Remove(entity.net.ID.Value);
}
}
}
private void OnExplosiveThrown(BasePlayer player, BaseEntity entity)
{
if (player == null || entity == null)
return;
if (entity.ShortPrefabName == "survey_charge.deployed" && configData.Options.IgnoreSurveyCharges)
return;
if (entity.ShortPrefabName == "flare.deployed" && configData.Options.IgnoreFlares)
return;
if (entity.ShortPrefabName == "grenade.supplysignal.deployed" && configData.Options.IgnoreSupplySignals)
return;
if (entity.ShortPrefabName == "grenade.beancan.deployed" && configData.Options.IgnoreBeanCans)
return;
AddPoints(player, Challenges.ExplosivesThrown, 1);
}
private void OnStructureRepair(BaseCombatEntity block, BasePlayer player)
{
if (player == null)
return;
if (block.health < block.MaxHealth())
AddPoints(player, Challenges.StructuresRepaired, 1);
}
#endregion
#region Hooks
[HookMethod("CompletedQuest")]
public void CompletedQuest(BasePlayer player)
{
CheckEntry(player);
AddPoints(player, Challenges.QuestsCompleted, 1);
}
#endregion
#region Functions
private void AddPoints(BasePlayer player, Challenges type, int amount)
{
if ((configData.Options.IgnoreAdmins && player.IsAdmin) || player.IsNpc || !player.userID.IsSteamId())
return;
CheckEntry(player);
statCache[player.userID].Stats[type] += amount;
CheckForUpdate(player.userID, type);
}
private void AddPoints(ulong playerId, Challenges type, int amount)
{
if (configData.Options.IgnoreAdmins)
{
if (ServerUsers.Get(playerId)?.group == ServerUsers.UserGroup.Moderator || ServerUsers.Get(playerId)?.group == ServerUsers.UserGroup.Owner)
return;
}
if (!playerId.IsSteamId())
return;
CheckEntry(playerId);
statCache[playerId].Stats[type] += amount;
CheckForUpdate(playerId, type);
}
private void AddDistance(BasePlayer player, Challenges type, int amount)
{
if ((configData.Options.IgnoreAdmins && player.IsAdmin) || player.IsNpc || !player.userID.IsSteamId())
return;
CheckEntry(player);
if (statCache[player.userID].Stats[type] < amount)
statCache[player.userID].Stats[type] = amount;
CheckForUpdate(player.userID, type);
}
private void CheckForUpdate(ulong playerId, Challenges type)
{
if (titleCache[type].UserID == playerId)
{
titleCache[type].Count = statCache[playerId].Stats[type];
return;
}
if (!configData.Options.UseUpdateTimer)
{
if (statCache[playerId].Stats[type] > titleCache[type].Count)
{
SwitchLeader(playerId, titleCache[type].UserID, type);
}
}
}
private void SwitchLeader(ulong newId, ulong oldId, Challenges type)
{
string name = GetGroupName(type);
if (configData.Options.UseOxideGroups)
{
if (oldId != 0UL && permission.GroupExists(name))
RemoveUserFromGroup(name, oldId.ToString());
if (newId != 0UL && permission.GroupExists(name))
AddUserToGroup(name, newId.ToString());
}
titleCache[type] = new LeaderData
{
Count = statCache[newId].Stats[type],
DisplayName = statCache[newId].DisplayName,
UserID = newId
};
if (configData.Options.AnnounceNewLeaders)
{
string message = MSG("newLeader")
.Replace("{playername}", $"<color={configData.Colors.TextColor1}>{statCache[newId].DisplayName}</color><color={configData.Colors.TextColor2}>")
.Replace("{ctype}", $"</color><color={configData.Colors.TextColor1}>{MSG(type.ToString())}</color>");
PrintToChat(message);
}
}
private void CheckUpdateTimer()
{
if ((GrabCurrentTime() - chData.LastUpdate) > configData.Options.UpdateTimer)
{
Dictionary<Challenges, UpdateInfo> updates = new Dictionary<Challenges, UpdateInfo>();
foreach (Challenges type in Enum.GetValues(typeof(Challenges)))
{
bool hasChanged = false;
UpdateInfo info = new UpdateInfo
{
newId = titleCache[(Challenges)type].UserID,
oldId = titleCache[(Challenges)type].UserID,
count = titleCache[(Challenges)type].Count
};
foreach (KeyValuePair<ulong, StatData> player in statCache)
{
if (info.oldId == player.Key)
continue;
if (player.Value.Stats[(Challenges)type] > info.count)
{
hasChanged = true;
info.newId = player.Key;
info.count = player.Value.Stats[(Challenges)type];
}
}
if (hasChanged)
SwitchLeader(info.newId, info.oldId, (Challenges)type);
}
}
else
{
double timeRemaining = ((configData.Options.UpdateTimer - (GrabCurrentTime() - chData.LastUpdate)) * 60) * 60;
timer.Once((int)timeRemaining + 10, () => CheckUpdateTimer());
}
}
class UpdateInfo
{
public ulong newId;
public ulong oldId;
public int count;
}
#endregion
#region Chat Commands
[ChatCommand("pc")]
private void cmdPC(BasePlayer player, string command, string[] args)
{
if (!UIDisabled)
CreateMenu(player);
else SendReply(player, MSG("UIDisabled", player.UserIDString));
}
[ChatCommand("pc_wipe")]
private void cmdPCWipe(BasePlayer player, string command, string[] args)
{
if (!player.IsAdmin) return;
RemoveAllUsergroups();
titleCache = new Dictionary<Challenges, LeaderData>();
statCache = new Dictionary<ulong, StatData>();
CheckValidData();
SendReply(player, MSG("dataWipe", player.UserIDString));
SaveData();
}
[ConsoleCommand("pc_wipe")]
private void ccmdPCWipe(ConsoleSystem.Arg arg)
{
if (arg.Connection != null) return;
RemoveAllUsergroups();
titleCache = new Dictionary<Challenges, LeaderData>();
statCache = new Dictionary<ulong, StatData>();
CheckValidData();
SendReply(arg, MSG("dataWipe"));
SaveData();
}
#endregion
#region Helper Methods
private void CheckEntry(BasePlayer player)
{
if (!statCache.ContainsKey(player.userID))
{
statCache.Add(player.userID, new StatData
{
DisplayName = player.displayName,
Stats = new Dictionary<Challenges, int>()
});
foreach (Challenges type in Enum.GetValues(typeof(Challenges)))
statCache[player.userID].Stats.Add((Challenges)type, 0);
}
}
private void CheckEntry(ulong playerId)
{
if (!statCache.ContainsKey(playerId))
{
statCache.Add(playerId, new StatData
{
DisplayName = covalence.Players.FindPlayerById(playerId.ToString())?.Name ?? "NoName",
Stats = new Dictionary<Challenges, int>()
});
foreach (Challenges type in Enum.GetValues(typeof(Challenges)))
statCache[playerId].Stats.Add((Challenges)type, 0);
}
}
private string GetGroupName(Challenges type) => configData.ChallengeSettings[type].Title;
private double GrabCurrentTime() => DateTime.UtcNow.Subtract(new DateTime(1970, 1, 1, 0, 0, 0)).Hours;
#endregion
#region Titles and Groups
private void RegisterGroups()
{
if (!configData.Options.UseOxideGroups) return;
foreach (Challenges type in Enum.GetValues(typeof(Challenges)))
RegisterGroup((Challenges)type);
}
private void RegisterGroup(Challenges type)
{
string name = GetGroupName(type);
if (!permission.GroupExists(name))
{
permission.CreateGroup(name, string.Empty, 0);
}
}
private void RegisterTitles()
{
if (!configData.Options.UseBetterChat || !BetterChat || !BetterChat.IsLoaded)
return;
BetterChat?.Call("API_RegisterThirdPartyTitle", new object[] { this, new Func<IPlayer, string>(GetPlayerTitles) });
}
private string GetPlayerTitles(IPlayer player)
{
if (!configData.Options.UseBetterChat) return string.Empty;
string playerTitle = string.Empty;
int count = 0;
IEnumerable<KeyValuePair<Challenges, LeaderData>> titles = titleCache.OrderByDescending(x => configData.ChallengeSettings[x.Key].Priority).Reverse();
foreach (KeyValuePair<Challenges, LeaderData> title in titles)
{
if (!configData.ChallengeSettings[title.Key].Enabled || title.Value.UserID == 0U) continue;
if (title.Value.UserID.ToString() == player.Id)
{
playerTitle += $"{(count > 0 ? " " : "")}{configData.Options.TagFormat.Replace("{TAG}", GetGroupName(title.Key))}";
count++;
if (count >= configData.Options.MaximumTags)
break;
}
}
return count == 0 ? string.Empty : $"[{configData.Colors.TitleColor}]{playerTitle}[/#]";
}
private void AddAllUsergroups()
{
if (configData.Options.UseOxideGroups)
{
foreach (KeyValuePair<Challenges, LeaderData> type in titleCache)
{
string name = GetGroupName(type.Key);
if (titleCache[type.Key].UserID == 0 || !GroupExists(name)) continue;
if (!UserInGroup(name, titleCache[type.Key].UserID.ToString()))
AddUserToGroup(name, titleCache[type.Key].UserID.ToString());
}
}
}
private void RemoveAllUsergroups()
{
if (configData.Options.UseOxideGroups)
{
foreach (KeyValuePair<Challenges, LeaderData> type in titleCache)
{
string name = GetGroupName(type.Key);
if (titleCache[type.Key].UserID == 0 || !GroupExists(name)) continue;
if (UserInGroup(name, titleCache[type.Key].UserID.ToString()))
RemoveUserFromGroup(name, titleCache[type.Key].UserID.ToString());
}
}
}
private bool GroupExists(string name) => permission.GroupExists(name);
private bool UserInGroup(string name, string playerId) => permission.UserHasGroup(playerId, name);
private void AddUserToGroup(string name, string playerId) => permission.AddUserGroup(playerId, name);
private void RemoveUserFromGroup(string name, string playerId) => permission.RemoveUserGroup(playerId, name);
#endregion
#region Config
private ConfigData configData;
class ConfigData
{
[JsonProperty(PropertyName = "Challenge Settings")]
public Dictionary<Challenges, ChallengeInfo> ChallengeSettings { get; set; }
public Option Options { get; set; }
public TextColor Colors { get; set; }
public class ChallengeInfo
{
[JsonProperty(PropertyName = "Title for name tag")]
public string Title;
[JsonProperty(PropertyName = "Enable this challenge")]
public bool Enabled;
[JsonProperty(PropertyName = "Position in the UI leaderboard")]
public int UIPosition;
[JsonProperty(PropertyName = "Title priority")]
public int Priority;
}
public class Option
{
[JsonProperty(PropertyName = "Ignore kills against sleeping players (Players killed)")]
public bool IgnoreSleepers;
[JsonProperty(PropertyName = "Kills against NPC players are counted seperate to player kills")]
public bool NPCKillSeperate;
[JsonProperty(PropertyName = "NPC kill distance counts as PVE distance")]
public bool NPCPVEKills;
[JsonProperty(PropertyName = "Show challenge leader title tags (Requires BetterChat)")]
public bool UseBetterChat;
[JsonProperty(PropertyName = "Ignore all statistics recorded by admins")]
public bool IgnoreAdmins;
[JsonProperty(PropertyName = "Ignore kills for event players (Players killed)")]
public bool IgnoreEventKills;
[JsonProperty(PropertyName = "Ignore supply signals thrown (Explosives thrown)")]
public bool IgnoreSupplySignals;
[JsonProperty(PropertyName = "Ignore survey charges thrown (Explosives thrown)")]
public bool IgnoreSurveyCharges;
[JsonProperty(PropertyName = "Ignore beancan grenades thrown (Explosives thrown)")]
public bool IgnoreBeanCans;
[JsonProperty(PropertyName = "Ignore flares thrown (Explosives thrown)")]
public bool IgnoreFlares;
[JsonProperty(PropertyName = "Broadcast new challenge leaders to chat")]
public bool AnnounceNewLeaders;
[JsonProperty(PropertyName = "Update leaders on a timer (Recommended)")]
public bool UseUpdateTimer;
[JsonProperty(PropertyName = "Create and use Oxide groups for each challenge type")]
public bool UseOxideGroups;
[JsonProperty(PropertyName = "Update timer (hours)")]
public int UpdateTimer;
[JsonProperty(PropertyName = "Maximum tags to display (Requires BetterChat)")]
public int MaximumTags;
[JsonProperty(PropertyName = "Format of tags displayed (Requires BetterChat)")]
public string TagFormat;
}
public class TextColor
{
[JsonProperty(PropertyName = "Primary message color (hex)")]
public string TextColor1;
[JsonProperty(PropertyName = "Secondary message color (hex)")]
public string TextColor2;
[JsonProperty(PropertyName = "Title color (hex) (Requires BetterChat)")]
public string TitleColor;
[JsonProperty(PropertyName = "UI Color - Background")]
public UIColor Background;
[JsonProperty(PropertyName = "UI Color - Panel")]
public UIColor Panel;
[JsonProperty(PropertyName = "UI Color - Button")]
public UIColor Button;
public class UIColor
{
[JsonProperty(PropertyName = "Color (hex)")]
public string Color;
[JsonProperty(PropertyName = "Alpha (0.0 - 1.0)")]
public float Alpha;
}
}
public Oxide.Core.VersionNumber Version { get; set; }
}
protected override void LoadConfig()
{
base.LoadConfig();
configData = Config.ReadObject<ConfigData>();
if (configData == null)
{
LoadDefaultConfig();
PrintWarning("Config was not readable! Using default values...");
}
if (configData.Version < Version)
UpdateConfigValues();
Config.WriteObject(configData, true);
}
protected override void LoadDefaultConfig() => configData = GetBaseConfig();
private ConfigData GetBaseConfig()
{
return new ConfigData
{
ChallengeSettings = new Dictionary<Challenges, ConfigData.ChallengeInfo>
{
{
Challenges.AnimalKills, new ConfigData.ChallengeInfo
{
Enabled = true,
Title = "Hunter",
UIPosition = 0,
Priority = 5
}
},
{
Challenges.ArrowKills, new ConfigData.ChallengeInfo
{
Enabled = true,
Title = "Archer",
UIPosition = 1,
Priority = 11
}
},
{
Challenges.StructuresBuilt, new ConfigData.ChallengeInfo
{
Enabled = true,
Title = "Architect",
UIPosition = 2,
Priority = 12
}
},
{
Challenges.ClothesCrafted, new ConfigData.ChallengeInfo
{
Enabled = true,
Title = "Tailor",
UIPosition = 3,
Priority = 19
}
},
{
Challenges.ExplosivesThrown, new ConfigData.ChallengeInfo
{
Enabled = true,
Title = "Bomb-tech",
UIPosition = 4,
Priority = 10
}
},
{
Challenges.Headshots, new ConfigData.ChallengeInfo
{
Enabled = true,
Title = "Assassin",
UIPosition = 5,
Priority = 1
}
},
{
Challenges.PlayersHealed, new ConfigData.ChallengeInfo
{
Enabled = true,
Title = "Medic",
UIPosition = 6,
Priority = 18
}
},
{
Challenges.PlayersKilled, new ConfigData.ChallengeInfo
{
Enabled = true,
Title = "Murderer",
UIPosition = 7,
Priority = 2
}
},
{
Challenges.MeleeKills, new ConfigData.ChallengeInfo
{
Enabled = true,
Title = "Fighter",
UIPosition = 8,
Priority = 3
}
},
{
Challenges.PlantsGathered, new ConfigData.ChallengeInfo
{
Enabled = true,
Title = "Harvester",
UIPosition = 9,
Priority = 17
}
},
{
Challenges.PVEKillDistance, new ConfigData.ChallengeInfo
{
Enabled = true,
Title = "Deadshot",
UIPosition = 10,
Priority = 6
}
},
{
Challenges.PVPKillDistance, new ConfigData.ChallengeInfo
{
Enabled = true,
Title = "Sniper",
UIPosition = 11,
Priority = 4
}
},
{
Challenges.StructuresRepaired, new ConfigData.ChallengeInfo
{
Enabled = true,
Title = "Handyman",
UIPosition = 12,
Priority = 13
}
},
{
Challenges.RevolverKills, new ConfigData.ChallengeInfo
{
Enabled = true,
Title = "Gunslinger",
UIPosition = 13,
Priority = 7
}
},
{
Challenges.RocketsFired, new ConfigData.ChallengeInfo
{
Enabled = true,
Title = "Rocketeer",
UIPosition = 14,
Priority = 8
}
},
{
Challenges.RocksGathered, new ConfigData.ChallengeInfo
{
Enabled = true,
Title = "Miner",
UIPosition = 15,
Priority = 16
}
},
{
Challenges.BladeKills, new ConfigData.ChallengeInfo
{
Enabled = true,
Title = "BladeKillsman",
UIPosition = 16,
Priority = 9
}
},
{
Challenges.WeaponsCrafted, new ConfigData.ChallengeInfo
{
Enabled = true,
Title = "Gunsmith",
UIPosition = 17,
Priority = 14
}
},
{
Challenges.WoodGathered, new ConfigData.ChallengeInfo
{
Enabled = true,
Title = "Lumberjack",
UIPosition = 18,
Priority = 15
}
},
{
Challenges.HelicopterKills, new ConfigData.ChallengeInfo
{
Enabled = true,
Title = "HeliHunter",
UIPosition = 19,
Priority = 20
}
},
{
Challenges.APCKills, new ConfigData.ChallengeInfo
{
Enabled = true,
Title = "TankHunter",
UIPosition = 20,
Priority = 21
}
},
{
Challenges.NPCKills, new ConfigData.ChallengeInfo
{
Enabled = true,
Title = "BotHunter",
UIPosition = 21,
Priority = 22
}
},
{
Challenges.QuestsCompleted, new ConfigData.ChallengeInfo
{
Enabled = true,
Title = "Adventurer",
UIPosition = 22,
Priority = 23
}
}
},
Options = new ConfigData.Option
{
AnnounceNewLeaders = false,
IgnoreAdmins = true,
IgnoreSleepers = true,
IgnoreSupplySignals = false,
NPCKillSeperate = true,
NPCPVEKills = true,
IgnoreSurveyCharges = false,
IgnoreFlares = true,
IgnoreEventKills = true,
MaximumTags = 2,
TagFormat = "[{TAG}]",
UseBetterChat = true,
UseOxideGroups = false,
UseUpdateTimer = false,
UpdateTimer = 168
},
Colors = new ConfigData.TextColor
{
TextColor1 = "#ce422b",
TextColor2 = "#939393",
TitleColor = "#ce422b",
Background = new ConfigData.TextColor.UIColor
{
Alpha = 0.98f,
Color = "#2b2b2b"
},
Panel = new ConfigData.TextColor.UIColor
{
Alpha = 1f,
Color = "#404141"
},
Button = new ConfigData.TextColor.UIColor
{
Alpha = 1f,
Color = "#393939"
}
},
Version = Version
};
}
protected override void SaveConfig() => Config.WriteObject(configData, true);
private void UpdateConfigValues()
{
PrintWarning("Config update detected! Updating config values...");
ConfigData baseConfig = GetBaseConfig();
if (configData.Version < new VersionNumber(2, 0, 30))
configData = baseConfig;
if (configData.Version < new VersionNumber(2, 0, 41))
{
configData.Options.IgnoreFlares = true;
configData.Options.NPCKillSeperate = true;
configData.Options.NPCPVEKills = true;
if (!configData.ChallengeSettings.ContainsKey(Challenges.APCKills))
configData.ChallengeSettings.Add(Challenges.APCKills, baseConfig.ChallengeSettings[Challenges.APCKills]);
if (!configData.ChallengeSettings.ContainsKey(Challenges.HelicopterKills))
configData.ChallengeSettings.Add(Challenges.HelicopterKills, baseConfig.ChallengeSettings[Challenges.HelicopterKills]);
if (!configData.ChallengeSettings.ContainsKey(Challenges.NPCKills))
configData.ChallengeSettings.Add(Challenges.NPCKills, baseConfig.ChallengeSettings[Challenges.NPCKills]);
}
configData.Version = Version;
PrintWarning("Config update completed!");
}
#endregion
#region Data Management
private void SaveData()
{
chData.Stats = statCache;
chData.Titles = titleCache;
data.WriteObject(chData);
}
private void LoadData()
{
try
{
chData = data.ReadObject<ChallengeData>();
statCache = chData.Stats;
titleCache = chData.Titles;
}
catch
{
chData = new ChallengeData();
}
}
private void CheckValidData()
{
if (titleCache.Count < Enum.GetValues(typeof(Challenges)).Length)
{
foreach (var type in Enum.GetValues(typeof(Challenges)))
{
if (!titleCache.ContainsKey((Challenges)type))
titleCache.Add((Challenges)type, new LeaderData());
}
}
for (int i = statCache.Count - 1; i >= 0; i--)
{
var player = statCache.ElementAt(i);
if (!player.Key.IsSteamId())
{
statCache.Remove(player.Key);
continue;
}
foreach (var type in Enum.GetValues(typeof(Challenges)))
{
if (!player.Value.Stats.ContainsKey((Challenges)type))
player.Value.Stats.Add((Challenges)type, 0);
}
}
}
private class ChallengeData
{
public Dictionary<ulong, StatData> Stats = new Dictionary<ulong, StatData>();
public Dictionary<Challenges, LeaderData> Titles = new Dictionary<Challenges, LeaderData>();
public double LastUpdate = 0;
}
private class StatData
{
public string DisplayName = string.Empty;
public Dictionary<Challenges, int> Stats = new Dictionary<Challenges, int>();
}
private class LeaderData
{
public ulong UserID = 0U;
public string DisplayName = null;
public int Count = 0;
}
private class WoundedData
{
public float distance;
public ulong attackerId;
}
enum Challenges
{
AnimalKills, ArrowKills, ClothesCrafted, Headshots, PlantsGathered, PlayersHealed, PlayersKilled, MeleeKills, RevolverKills, RocketsFired, RocksGathered, BladeKills, StructuresBuilt, StructuresRepaired, ExplosivesThrown, WeaponsCrafted, WoodGathered, QuestsCompleted, PVPKillDistance, PVEKillDistance, HelicopterKills, APCKills, NPCKills
}
#endregion
#region Lists
List<string> meleeShortnames = new List<string> { "bone.club", "hammer.salvaged", "hatchet", "icepick.salvaged", "knife.bone", "mace", "machete", "pickaxe", "rock", "stone.pickaxe", "stonehatchet", "torch" };
List<string> bladeShortnames = new List<string> { "salvaged.sword", "salvaged.cleaver", "longsword", "axe.salvaged" };
List<string> plantShortnames = new List<string> { "pumpkin", "cloth", "corn", "mushroom", "seed.hemp", "seed.corn", "seed.pumpkin" };
#endregion
#region Messaging
private string MSG(string key, string id = null) => lang.GetMessage(key, this, id);
Dictionary<string, string> Messages = new Dictionary<string, string>
{
{"newLeader", "{playername} has topped the leader board for most {ctype}" },
{"AnimalKills", "animal kills" },
{"ArrowKills", "kills with arrows" },
{"ClothesCrafted", "clothes crafted" },
{"Headshots", "headshots" },
{"PlantsGathered", "plants gathered" },
{"PlayersHealed", "players healed" },
{"PlayersKilled", "players killed" },
{"MeleeKills", "melee kills" },
{"RevolverKills", "revolver kills" },
{"RocketsFired", "rockets fired" },
{"RocksGathered", "ore gathered" },
{"BladeKills", "blade kills" },
{"StructuresBuilt", "structures built" },
{"StructuresRepaired", "structures repaired" },
{"ExplosivesThrown", "explosives thrown" },
{"WeaponsCrafted", "weapons crafted" },
{"WoodGathered", "wood gathered" },
{"PVEKillDistance", "longest PVE kill"},
{"PVPKillDistance", "longest PVP kill" },
{"QuestsCompleted", "quests completed" },
{"UITitle", "Player Challenges v{Version}" },
{"UIDisabled", "The UI has been disabled as there is a error in the config. Please contact a admin" },
{"dataWipe", "You have wiped all player stats and titles" }
};
#endregion
}
}