C#:
/*
* Copyright (C) 2024 Game4Freak.io
* This mod is provided under the Game4Freak EULA.
* Full legal terms can be found at https://game4freak.io/eula/
*/
using Newtonsoft.Json;
using Newtonsoft.Json.Converters;
using Oxide.Core;
using Oxide.Core.Plugins;
using System.Collections.Generic;
using System.Linq;
using UnityEngine;
namespace Oxide.Plugins
{
[Info("Kill Awards", "VisEntities", "1.4.0")]
[Description("Reward players with items, kits, XP, and more for reaching kill milestones.")]
public class KillAwards : RustPlugin
{
#region 3rd Party Dependencies
[PluginReference]
private readonly Plugin Kits, Economics, ServerRewards, XPerience, SkillTree;
#endregion 3rd Party Dependencies
#region Fields
private static KillAwards _plugin;
private static Configuration _config;
private StoredData _storedData;
#endregion Fields
#region Configuration
private class Configuration
{
[JsonProperty("Version")]
public string Version { get; set; }
[JsonProperty("Count NPC Kills Towards Milestones")]
public bool CountNpcKills { get; set; }
[JsonProperty("Count Animal Kills Towards Milestones")]
public bool CountAnimalKills { get; set; }
[JsonProperty("Exclude Teammate Kills From Count")]
public bool ExcludeTeammateKills { get; set; }
[JsonProperty("Reset Kill Count On Player Death")]
public bool ResetKillCountOnDeath { get; set; }
[JsonProperty("Kill Milestones")]
public Dictionary<int, MilestoneRewardConfig> Milestones { get; set; } = new Dictionary<int, MilestoneRewardConfig>();
}
private class MilestoneRewardConfig
{
[JsonProperty("Health To Restore (0 to disable)")]
public float HealthToRestore { get; set; }
[JsonProperty("Refill Held Weapon Ammo")]
public bool RefillWeaponAmmo { get; set; }
[JsonProperty("Kit Name (requires Kits plugin, empty to disable)")]
public string KitName { get; set; }
[JsonProperty("Server Rewards Points (requires Server Rewards plugin, 0 to disable)")]
public int ServerRewardsPoints { get; set; }
[JsonProperty("Economics Money (requires Economics plugin, 0 to disable)")]
public int EconomicsMoney { get; set; }
[JsonProperty("XPerience XP (requires XPerience plugin, 0 to disable)")]
public double XPerienceXp { get; set; }
[JsonProperty("Skill Tree XP (requires Skill Tree plugin, 0 to disable)")]
public double SkillTreeXp { get; set; }
[JsonProperty("Personal Message (shown only to player, supports {playerName} and {killCount})")]
public string PersonalMessage { get; set; }
[JsonProperty("Broadcast Message (shown to all players, supports {playerName} and {killCount})")]
public string BroadcastMessage { get; set; }
[JsonProperty("Commands To Execute")]
public List<CommandEntry> Commands { get; set; }
}
private class CommandEntry
{
[JsonProperty("Command Type (Chat, Server, or Client)")]
[JsonConverter(typeof(StringEnumConverter))]
public CommandType Type { get; set; }
[JsonProperty("Command (supports {playerId}, {playerName}, {positionX}, {positionY}, {positionZ}, {grid})")]
public string Command { get; set; }
}
protected override void LoadConfig()
{
base.LoadConfig();
_config = Config.ReadObject<Configuration>();
if (string.Compare(_config.Version, Version.ToString()) < 0)
UpdateConfig();
SaveConfig();
}
protected override void LoadDefaultConfig()
{
_config = GetDefaultConfig();
}
protected override void SaveConfig()
{
Config.WriteObject(_config, true);
}
private void UpdateConfig()
{
PrintWarning("Config changes detected! Updating...");
Configuration defaultConfig = GetDefaultConfig();
if (string.Compare(_config.Version, "1.0.0") < 0)
_config = defaultConfig;
if (string.Compare(_config.Version, "1.1.0") < 0)
{
foreach (var milestone in _config.Milestones)
{
milestone.Value.EconomicsMoney = 0;
milestone.Value.ServerRewardsPoints = 0;
}
}
PrintWarning("Config update complete! Updated from version " + _config.Version + " to " + Version.ToString());
_config.Version = Version.ToString();
}
private Configuration GetDefaultConfig()
{
return new Configuration
{
Version = Version.ToString(),
CountNpcKills = false,
CountAnimalKills = false,
ExcludeTeammateKills = true,
ResetKillCountOnDeath = true,
Milestones = new Dictionary<int, MilestoneRewardConfig>
{
{
1, new MilestoneRewardConfig
{
HealthToRestore = 10.0f,
RefillWeaponAmmo = false,
KitName = "",
ServerRewardsPoints = 0,
EconomicsMoney = 0,
XPerienceXp = 0,
SkillTreeXp = 0,
PersonalMessage = "You reached {killCount} kills, nice start!",
BroadcastMessage = "{playerName} just hit 1 kill milestone!",
Commands = new List<CommandEntry>()
}
},
{
2, new MilestoneRewardConfig
{
HealthToRestore = 15.0f,
RefillWeaponAmmo = true,
KitName = "",
ServerRewardsPoints = 0,
EconomicsMoney = 0,
XPerienceXp = 0,
SkillTreeXp = 0,
PersonalMessage = "You reached {killCount} kills! Keep it going!",
BroadcastMessage = "{playerName} is on a roll with {killCount} kills!",
Commands = new List<CommandEntry>()
}
},
{
3, new MilestoneRewardConfig
{
HealthToRestore = 20.0f,
RefillWeaponAmmo = true,
KitName = "",
ServerRewardsPoints = 0,
EconomicsMoney = 0,
XPerienceXp = 0,
SkillTreeXp = 0,
PersonalMessage = "You reached {killCount} kills and earned 50 scrap!",
BroadcastMessage = "Watch out! {playerName} just reached {killCount} kills!",
Commands = new List<CommandEntry>
{
new CommandEntry
{
Type = CommandType.Server,
Command = "inventory.giveto {playerId} scrap 50"
}
}
}
}
}
};
}
#endregion Configuration
#region Stored Data
private class StoredData
{
[JsonProperty("Players")]
public Dictionary<ulong, PlayerData> Players { get; set; } = new Dictionary<ulong, PlayerData>();
}
private class PlayerData
{
[JsonProperty("Kills")]
public int Kills { get; set; }
}
private void LoadData()
{
_storedData = Interface.Oxide.DataFileSystem.ReadObject<StoredData>(Name);
if (_storedData == null)
{
_storedData = new StoredData();
}
}
private void SaveData()
{
Interface.Oxide.DataFileSystem.WriteObject(Name, _storedData);
}
private void ClearData()
{
Interface.Oxide.DataFileSystem.DeleteDataFile(Name);
}
#endregion Stored Data
#region Oxide Hooks
private void Init()
{
_plugin = this;
LoadData();
}
private void Unload()
{
_config = null;
_plugin = null;
}
private void OnNewSave()
{
ClearData();
}
private void OnEntityDeath(BaseEntity victim, HitInfo deathInfo)
{
if (victim == null || deathInfo == null)
return;
if (victim is BasePlayer victimPlayer && _config.ResetKillCountOnDeath)
{
if (_storedData.Players.TryGetValue(victimPlayer.userID, out PlayerData victimData))
{
victimData.Kills = 0;
SaveData();
}
}
BasePlayer killer = deathInfo.InitiatorPlayer;
if (killer == null || killer == victim || !killer.userID.IsSteamId())
return;
if (victim is BasePlayer victimBasePlayer)
{
if (_config.ExcludeTeammateKills && AreTeammates(killer.userID, victimBasePlayer.userID))
return;
if (victimBasePlayer.IsNpc && !_config.CountNpcKills)
return;
}
else if (victim is BaseAnimalNPC)
{
if (!_config.CountAnimalKills)
return;
}
else
{
return;
}
ProcessKillReward(killer);
}
#endregion Oxide Hooks
#region Reward Processing
private void ProcessKillReward(BasePlayer player)
{
if (!_storedData.Players.TryGetValue(player.userID, out PlayerData playerData))
{
playerData = new PlayerData();
_storedData.Players[player.userID] = playerData;
}
playerData.Kills++;
int maxMilestone = _config.Milestones.Keys.Max();
if (playerData.Kills > maxMilestone)
playerData.Kills = 0;
if (_config.Milestones.TryGetValue(playerData.Kills, out MilestoneRewardConfig reward))
{
GrantHealthReward(player, reward);
GrantAmmoRefill(player, reward);
GrantKit(player, reward);
GrantServerRewardsPoints(player, reward);
GrantEconomicsMoney(player, reward);
GrantXPerienceXp(player, reward);
GrantSkillTreeXp(player, reward);
ExecuteCommands(player, reward);
SendPersonalMessage(player, reward, playerData.Kills);
SendBroadcastMessage(player, reward, playerData.Kills);
}
SaveData();
}
private void GrantHealthReward(BasePlayer player, MilestoneRewardConfig reward)
{
if (reward.HealthToRestore <= 0)
return;
player.Heal(reward.HealthToRestore);
SendReplyLocalized(player, Lang.Reward_HealthRestored, reward.HealthToRestore);
}
private void GrantAmmoRefill(BasePlayer player, MilestoneRewardConfig reward)
{
if (!reward.RefillWeaponAmmo)
return;
RefillActiveWeaponAmmo(player);
SendReplyLocalized(player, Lang.Reward_AmmoRefilled);
}
private void GrantKit(BasePlayer player, MilestoneRewardConfig reward)
{
if (string.IsNullOrEmpty(reward.KitName))
return;
if (KitsUtil.GiveKit(player, reward.KitName))
{
SendReplyLocalized(player, Lang.Reward_KitGiven, reward.KitName);
}
}
private void GrantServerRewardsPoints(BasePlayer player, MilestoneRewardConfig reward)
{
if (reward.ServerRewardsPoints <= 0 || !IsPluginLoaded(ServerRewards))
return;
ServerRewards.Call("AddPoints", player.userID, reward.ServerRewardsPoints);
SendReplyLocalized(player, Lang.Reward_PointsAwarded, reward.ServerRewardsPoints);
}
private void GrantEconomicsMoney(BasePlayer player, MilestoneRewardConfig reward)
{
if (reward.EconomicsMoney <= 0 || !IsPluginLoaded(Economics))
return;
Economics.Call("Deposit", player.userID, (double)reward.EconomicsMoney);
SendReplyLocalized(player, Lang.Reward_MoneyAwarded, reward.EconomicsMoney);
}
private void GrantXPerienceXp(BasePlayer player, MilestoneRewardConfig reward)
{
if (reward.XPerienceXp <= 0 || !IsPluginLoaded(XPerience))
return;
XPerienceUtil.GiveXp(player, reward.XPerienceXp);
SendReplyLocalized(player, Lang.Reward_XPerienceXpAwarded, reward.XPerienceXp);
}
private void GrantSkillTreeXp(BasePlayer player, MilestoneRewardConfig reward)
{
if (reward.SkillTreeXp <= 0 || !IsPluginLoaded(SkillTree))
return;
SkillTreeUtil.AwardXp(player, reward.SkillTreeXp);
SendReplyLocalized(player, Lang.Reward_SkillTreeXpAwarded, reward.SkillTreeXp);
}
private void ExecuteCommands(BasePlayer player, MilestoneRewardConfig reward)
{
if (reward.Commands == null)
return;
foreach (CommandEntry commandEntry in reward.Commands)
{
ExecuteCommand(player, commandEntry.Type, commandEntry.Command);
}
}
private void SendPersonalMessage(BasePlayer player, MilestoneRewardConfig reward, int killCount)
{
if (string.IsNullOrEmpty(reward.PersonalMessage))
return;
string message = FormatMessage(reward.PersonalMessage, player, killCount);
SendReplyLocalized(player, message);
}
private void SendBroadcastMessage(BasePlayer player, MilestoneRewardConfig reward, int killCount)
{
if (string.IsNullOrEmpty(reward.BroadcastMessage))
return;
string message = FormatMessage(reward.BroadcastMessage, player, killCount);
foreach (BasePlayer activePlayer in BasePlayer.activePlayerList)
{
if (activePlayer != null)
SendReplyLocalized(activePlayer, message);
}
}
#endregion Reward Processing
#region Helper Functions
private bool AreTeammates(ulong firstPlayerId, ulong secondPlayerId)
{
RelationshipManager.PlayerTeam team = RelationshipManager.ServerInstance.FindPlayersTeam(firstPlayerId);
if (team != null && team.members.Contains(secondPlayerId))
{
return true;
}
return false;
}
private void RefillActiveWeaponAmmo(BasePlayer player)
{
var activeItem = player.GetActiveItem();
if (activeItem == null)
return;
BaseProjectile weapon = activeItem.GetHeldEntity() as BaseProjectile;
if (weapon == null || weapon.primaryMagazine == null)
return;
weapon.SetAmmoCount(weapon.primaryMagazine.capacity);
weapon.SendNetworkUpdateImmediate();
}
private string FormatMessage(string text, BasePlayer player, int killCount)
{
return text
.Replace("{playerName}", player.displayName)
.Replace("{killCount}", killCount.ToString());
}
public static bool IsPluginLoaded(Plugin plugin)
{
if (plugin != null && plugin.IsLoaded)
{
return true;
}
return false;
}
#endregion Helper Functions
#region 3rd Party Integration
public static class KitsUtil
{
public static bool GiveKit(BasePlayer player, string kitName)
{
if (!IsPluginLoaded(_plugin.Kits))
return false;
if (player == null || string.IsNullOrEmpty(kitName))
return false;
object result = _plugin.Kits.Call("GiveKit", player, kitName);
if (result is bool)
{
return (bool)result;
}
return false;
}
}
public static class XPerienceUtil
{
public static void GiveXp(BasePlayer player, double amount)
{
if (!IsPluginLoaded(_plugin.XPerience))
return;
if (player == null || amount <= 0)
return;
_plugin.XPerience.Call("GiveXP", player, amount);
}
}
public static class SkillTreeUtil
{
public static void AwardXp(BasePlayer player, double xpAmount)
{
if (!IsPluginLoaded(_plugin.SkillTree))
return;
if (player == null || xpAmount <= 0)
return;
_plugin.SkillTree.Call("AwardXP", player.userID.Get(), xpAmount, "KillAwards", false);
}
}
#endregion 3rd Party Integration
#region Command Execution
private enum CommandType
{
Chat,
Server,
Client
}
private void ExecuteCommand(BasePlayer player, CommandType type, string command)
{
string formattedCommand = command
.Replace("{playerId}", player.UserIDString)
.Replace("{playerName}", player.displayName)
.Replace("{positionX}", player.transform.position.x.ToString())
.Replace("{positionY}", player.transform.position.y.ToString())
.Replace("{positionZ}", player.transform.position.z.ToString())
.Replace("{grid}", MapHelper.PositionToString(player.transform.position));
switch (type)
{
case CommandType.Chat:
player.Command(string.Format("chat.say \"{0}\"", formattedCommand));
break;
case CommandType.Client:
player.Command(formattedCommand);
break;
case CommandType.Server:
Server.Command(formattedCommand);
break;
}
}
#endregion Command Execution
#region Localization
private static class Lang
{
public const string Reward_HealthRestored = "Reward.HealthRestored";
public const string Reward_AmmoRefilled = "Reward.AmmoRefilled";
public const string Reward_KitGiven = "Reward.KitGiven";
public const string Reward_PointsAwarded = "Reward.PointsAwarded";
public const string Reward_MoneyAwarded = "Reward.MoneyAwarded";
public const string Reward_XPerienceXpAwarded = "Reward.XPerienceXpAwarded";
public const string Reward_SkillTreeXpAwarded = "Reward.SkillTreeXpAwarded";
}
protected override void LoadDefaultMessages()
{
lang.RegisterMessages(new Dictionary<string, string>
{
[Lang.Reward_HealthRestored] = "You have been healed for <color=#90EE90>{0}</color> health!",
[Lang.Reward_AmmoRefilled] = "Your weapon ammo has been fully refilled!",
[Lang.Reward_KitGiven] = "You have received the <color=#87CEEB>{0}</color> kit!",
[Lang.Reward_PointsAwarded] = "You have been awarded <color=#FFD700>{0}</color> Server Rewards points!",
[Lang.Reward_MoneyAwarded] = "You have been awarded <color=#FFD700>{0}</color> money!",
[Lang.Reward_XPerienceXpAwarded] = "You have been awarded <color=#DDA0DD>{0}</color> XPerience XP!",
[Lang.Reward_SkillTreeXpAwarded] = "You have been awarded <color=#DDA0DD>{0}</color> SkillTree XP!",
}, this, "en");
}
private static string GetMessage(BasePlayer player, string messageKey, params object[] args)
{
string userId;
if (player != null)
userId = player.UserIDString;
else
userId = null;
string message = _plugin.lang.GetMessage(messageKey, _plugin, userId);
if (args.Length > 0)
message = string.Format(message, args);
return message;
}
public static void SendReplyLocalized(BasePlayer player, string messageKey, params object[] args)
{
string message = GetMessage(player, messageKey, args);
if (!string.IsNullOrWhiteSpace(message))
_plugin.SendReply(player, message);
}
#endregion Localization
}
}