C#:
using System;
using System.Collections.Generic;
using Oxide.Core;
using Oxide.Core.Plugins;
using UnityEngine;
namespace Oxide.Plugins
{
[Info("XP Levels", "0killrest", "1.0.0")]
[Description("Players gain XP for actions and level up to gain more max health.")]
internal class XPLevels : RustPlugin
{
#region Configuration
private static ConfigData config;
private class ConfigData
{
public int MaxLevel { get; set; } = 100;
public float HealthPerLevel { get; set; } = 5f;
public int XPPerAction { get; set; } = 10;
public static ConfigData GetDefault()
{
return new ConfigData();
}
}
protected override void LoadConfig()
{
base.LoadConfig();
config = Config.ReadObject<ConfigData>();
if (config == null)
{
LoadDefaultConfig();
}
}
protected override void LoadDefaultConfig()
{
config = ConfigData.GetDefault();
PrintWarning("Default configuration created.");
}
protected override void SaveConfig() => Config.WriteObject(config);
#endregion
#region Data Storage
private Dictionary<ulong, PlayerData> playerData;
private class PlayerData
{
public int Level { get; set; } = 1;
public int XP { get; set; } = 0;
public int TotalXP { get; set; } = 0;
}
private void LoadData()
{
playerData = Interface.Oxide.DataFileSystem.ReadObject<Dictionary<ulong, PlayerData>>("XPLevelsData") ?? new Dictionary<ulong, PlayerData>();
}
private void SaveData()
{
Interface.Oxide.DataFileSystem.WriteObject("XPLevelsData", playerData);
}
#endregion
#region Hooks
private void Init()
{
LoadData();
permission.RegisterPermission("xplevels.admin", this);
}
private void OnServerSave() => SaveData();
private void Unload() => SaveData();
private void OnPlayerInit(BasePlayer player)
{
GetPlayerData(player.userID);
UpdatePlayerHealth(player);
}
private void OnPlayerAttack(BasePlayer attacker, HitInfo info)
{
if (attacker == null || info?.HitEntity == null) return;
BasePlayer victim = info.HitEntity as BasePlayer;
if (victim != null && victim != attacker)
{
// Player kill
AddXP(attacker.userID, config.XPPerAction * 3);
}
else if (info.HitEntity is ResourceEntity)
{
// Gathering resources
AddXP(attacker.userID, config.XPPerAction);
}
}
private void OnItemCrafted(ItemCraftedEvent craftEvent)
{
if (craftEvent?.crafterIPlayer?.Object is BasePlayer player)
{
AddXP(player.userID, config.XPPerAction);
}
}
private void OnPlayerDeath(BasePlayer player, HitInfo info)
{
if (player == null) return;
// Punish: lose 5% of current level XP, but not less than 0
if (playerData.ContainsKey(player.userID))
{
int loss = (int)(playerData[player.userID].XP * 0.05);
playerData[player.userID].XP = Math.Max(0, playerData[player.userID].XP - loss);
SendReply(player, $"<color=red>You died and lost {loss} XP!</color>");
CheckLevelUp(player.userID, player);
}
}
#endregion
#region Core Logic
private PlayerData GetPlayerData(ulong userId)
{
if (!playerData.ContainsKey(userId))
{
playerData[userId] = new PlayerData();
SaveData();
}
return playerData[userId];
}
private void AddXP(ulong userId, int amount)
{
var data = GetPlayerData(userId);
data.XP += amount;
data.TotalXP += amount;
BasePlayer player = BasePlayer.FindByID(userId);
if (player != null && CheckLevelUp(userId, player))
{
SendReply(player, $"<color=green>Level Up!</color> You are now level {data.Level}!");
UpdatePlayerHealth(player);
}
// Message about gaining XP
if (player != null)
{
SendReply(player, $"+{amount} XP (Total: {data.XP}/{GetXPRequiredForNextLevel(data.Level)})");
}
SaveData();
}
private bool CheckLevelUp(ulong userId, BasePlayer player)
{
var data = playerData[userId];
bool leveledUp = false;
while (data.Level < config.MaxLevel && data.XP >= GetXPRequiredForNextLevel(data.Level))
{
data.XP -= GetXPRequiredForNextLevel(data.Level);
data.Level++;
leveledUp = true;
}
return leveledUp;
}
private int GetXPRequiredForNextLevel(int currentLevel)
{
// Formula: 100 * Level (Level 1 -> 100 XP, Level 2 -> 200 XP)
return currentLevel * 100;
}
private void UpdatePlayerHealth(BasePlayer player)
{
var data = GetPlayerData(player.userID);
float maxHealth = 100f + (data.Level - 1) * config.HealthPerLevel;
player.InitializeHealth(maxHealth, maxHealth);
player.SendNetworkUpdate();
}
#endregion
#region Commands
[ChatCommand("stats")]
private void cmdStats(BasePlayer player, string command, string[] args)
{
var data = GetPlayerData(player.userID);
int nextLevelXP = GetXPRequiredForNextLevel(data.Level);
SendReply(player, $"<color=#00ff00>Level: {data.Level}</color> | XP: {data.XP}/{nextLevelXP} | Total XP Earned: {data.TotalXP}");
}
[ConsoleCommand("xplevels.setlevel")]
private void cmdSetLevel(ConsoleSystem.Arg args)
{
if (args.Connection != null && !permission.UserHasPermission(args.Connection.UserId.ToString(), "xplevels.admin")) return;
ulong userId = args.GetULong(0);
int newLevel = args.GetInt(1);
if (userId == 0 || newLevel < 1) return;
var data = GetPlayerData(userId);
data.Level = Math.Min(newLevel, config.MaxLevel);
data.XP = 0;
BasePlayer player = BasePlayer.FindByID(userId);
if (player != null) UpdatePlayerHealth(player);
args.ReplyWith($"Set level for {userId} to {data.Level}");
SaveData();
}
#endregion
}
}