Experience manager component that listens for kill events and awards experience to the attacker, applies experience to player stats, and handles leveling logic including sending a level-up scene event.
using Sandbox.Diagnostics;
using Sandbox.Events;
namespace KOTH.PlayerExp;
public class ExpManager : SingletonComponent<ExpManager>,
IGameEventHandler<KillBroadcastEvent>
{
private readonly float _levelFactor = 1.8f;
private readonly int _firstLevelExp = 25;
public void OnGameEvent(KillBroadcastEvent KillEvent)
{
if (!Networking.IsHost)
{
return;
}
var DamageEvent = KillEvent.DamageEvent;
if (DamageEvent.IsDummyDamage)
{
return;
}
var PlayerState = DamageEvent.AttackerPlayerState;
if (!PlayerState.IsValid() || PlayerState == DamageEvent.VictimPlayerState)
{
return;
}
FExpEvent ExpEvent = new()
{
Amount = CalculateExp(3, 1),
Origin = ExpOrigins.Kill,
};
Log.Info($"Broadcasting exp event {ExpEvent} to {PlayerState}:{PlayerState.SteamId}");
using (Rpc.FilterInclude(PlayerState.Connection))
{
LocalProcessExpEvent(ExpEvent);
}
}
/// <summary>
/// Calculates exp
/// </summary>
/// <param name="baseExp">the lowest the exp could be</param>
/// <param name="entropy">the highest the exp could be</param>
/// <returns>a number in between the two values</returns>
public int CalculateExp(int baseExp, int entropy)
{
var random = new Random();
return random.Next(baseExp, baseExp + entropy + 1);
}
/// <summary>
/// Processes the exp event and call the increment function for exp
/// </summary>
/// <param name="expEvent">The event received from the server</param>
[Rpc.Broadcast]
private void LocalProcessExpEvent(FExpEvent expEvent)
{
Log.Info($"Received exp event {expEvent.Amount} from {expEvent.Origin}");
Sandbox.Services.Stats.Increment("player_exp", expEvent.Amount/*, "origin", expEvent.Origin.ToString()*/);
Sandbox.Services.Stats.Increment("player_exp_current", expEvent.Amount/*, "origin", expEvent.Origin.ToString()*/);
CheckLevelUp();
}
/// <summary>
/// Checks if the player has accumulated enough experience points to level up.
/// If the threshold for leveling up is met or exceeded, the player's level is incremented,
/// and the current experience value is adjusted accordingly.
/// </summary>
public void CheckLevelUp()
{
var exp = Sandbox.Services.Stats.LocalPlayer.Get("player_exp_current");
var level = GetCurrentLocalLevel();
// probably dont need to use floor but its safer and stops rounding errors
var threshold = (int)Math.Floor((level.LastValue * _firstLevelExp) / _levelFactor);
if (exp.Sum >= threshold)
{
// sets value instead of increment
Sandbox.Services.Stats.SetValue("player_level", level.LastValue + 1);
Sandbox.Services.Stats.SetValue("player_exp_current", exp.Sum > threshold ? exp.Sum - threshold : 0);
Log.Info($"Player leveled up: {level.LastValue + 1}");
Scene.Dispatch(new LevelUpEvent((int) level.LastValue + 1));
}
}
public static Sandbox.Services.Stats.PlayerStat GetCurrentLocalLevel()
{
return Sandbox.Services.Stats.LocalPlayer.Get("player_level");
}
public static string GetLevelString(ulong SteamId)
{
var Stats = Sandbox.Services.Stats.GetPlayerStats(Game.Ident, (long)SteamId);
Stats.Refresh();
return Stats.Get("player_level").LastValue.ToString();
}
}