using LabApi.Features; using LabApi.Loader.Features.Plugins; using System.Diagnostics.CodeAnalysis; using System.Reflection; using System.Runtime.InteropServices; using LabApi.Events.Arguments.PlayerEvents; using LabApi.Events.Handlers; using LabApi.Features.Console; namespace StatsTracker { [SuppressMessage("ReSharper", "InconsistentNaming")] public struct PlayerStat { public uint kills; public uint deaths; public uint team_damage; } [SuppressMessage("ReSharper", "InconsistentNaming")] public struct ItemStat { public string item_name; public uint item_count; } [SuppressMessage("ReSharper", "InconsistentNaming")] public struct Player { public string player_id; public PlayerStat player_stats; public ItemStat[] player_items; } [SuppressMessage("ReSharper", "InconsistentNaming")] public class StatsTracker : Plugin { public override string Name => "StatsTracker"; public override string Author => "Code002Lover"; public override Version Version { get; } = new(1, 0, 0); public override string Description => "Tracks stats for players."; public override Version RequiredApiVersion { get; } = new(LabApiProperties.CompiledVersion); private const string RustDllName = "stats_tracker"; [DllImport(RustDllName, EntryPoint="get_player_stats", CallingConvention = CallingConvention.Cdecl)] private static extern ref Player GetPlayerStats(ref string player_id); [DllImport(RustDllName, EntryPoint="save_player_stats", CallingConvention = CallingConvention.Cdecl)] private static extern bool SavePlayerStats(ref Player player); public override void Enable() { var pathVariable = Environment.GetEnvironmentVariable("PATH"); Logger.Debug($"PATH: {pathVariable}"); var extractedDllPath = ExtractRustDll(); // Call extraction if (string.IsNullOrEmpty(extractedDllPath)) { Logger.Error("Failed to extract Rust DLL. Exiting."); return; } var libDirectory = Path.GetDirectoryName(extractedDllPath); if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) { SetDllDirectory(libDirectory); Logger.Info($"Windows: Added '{libDirectory}' to DLL search path."); } else if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux)) { Environment.SetEnvironmentVariable("LD_LIBRARY_PATH", libDirectory + ":" + Environment.GetEnvironmentVariable("LD_LIBRARY_PATH")); Logger.Info($"Linux: Extracted library to '{libDirectory}'. Relying on default search paths and manual LD_LIBRARY_PATH."); } else { Logger.Error("Only windows and linux are supported."); return; } PlayerEvents.Death += OnPlayerDied; PlayerEvents.UsedItem += OnItemUsed; PlayerEvents.Hurt += OnHurt; } // Conditional P/Invoke for OS-specific functions (like SetDllDirectory on Windows) [DllImport("kernel32.dll", CharSet = CharSet.Auto, SetLastError = true, ExactSpelling = true)] [return: MarshalAs(UnmanagedType.Bool)] private static extern bool SetDllDirectory(string lpPathName); private static string ExtractRustDll() { string dllFilename; string resourceSubPath; // Folder inside NativeLibs string extractedLibFilename; if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) { dllFilename = $"{RustDllName}.dll"; resourceSubPath = "x86_64_pc_windows_gnu"; extractedLibFilename = dllFilename; // On Windows, we keep the original name } else if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux)) { dllFilename = $"lib{RustDllName}.so"; // Linux uses lib prefix and .so suffix resourceSubPath = "x86_64_unknown_linux_gnu"; extractedLibFilename = dllFilename; // On Linux, keep the lib*.so name } else { Logger.Error("Unsupported operating system detected."); return null; } // Determine where to extract the DLL. var targetDirectory = AppDomain.CurrentDomain.BaseDirectory; var extractedLibPath = Path.Combine(targetDirectory, extractedLibFilename); // Check if the DLL already exists (e.g., from a previous run or development environment) if (File.Exists(extractedLibPath)) { Logger.Warn($"Rust lib '{dllFilename}' already exists at '{extractedLibPath}'. Skipping extraction."); return extractedLibPath; // Return the path directly if it exists } try { // Adjust this resource name to match your actual embedded resource name. // Based on your previous comment: var resourceName = $"StatsTracker.Rust.target.{resourceSubPath}.release.{dllFilename}"; Logger.Info($"Attempting to load embedded resource: {resourceName}"); using (var stream = Assembly.GetExecutingAssembly().GetManifestResourceStream(resourceName)) { if (stream == null) { Logger.Error($"Error: Embedded resource '{resourceName}' not found."); Logger.Info("Available resources:"); foreach (var res in Assembly.GetExecutingAssembly().GetManifestResourceNames()) { Logger.Info($"- {res}"); } return null; // Return null to indicate failure } using (var fileStream = File.Create(extractedLibPath)) { stream.CopyTo(fileStream); } } Logger.Info($"Successfully extracted '{RustDllName}' to '{extractedLibPath}'."); return extractedLibPath; // Return the path after successful extraction } catch (Exception ex) { Logger.Error($"Error extracting Rust DLL to {extractedLibPath}: {ex.Message}"); return null; // Return null to indicate failure } } public override void Disable() { PlayerEvents.Death -= OnPlayerDied; PlayerEvents.UsedItem -= OnItemUsed; } private static void OnHurt(PlayerHurtEventArgs ev) { switch (ev.Attacker) { case null: case { DoNotTrack: true }: return; } if(ev.Attacker.Team != ev.Player.Team) return; var userId = ev.Attacker.UserId; var killerStats = GetPlayerStats(ref userId); killerStats.player_stats.team_damage++; SavePlayerStats(ref killerStats); } private static void OnPlayerDied(PlayerDeathEventArgs ev) { if (ev.Attacker != null && ev.Attacker.Nickname != "emeraldo" && !ev.Attacker.DoNotTrack) { var userId = ev.Attacker.UserId; var killerStats = GetPlayerStats(ref userId); killerStats.player_stats.kills++; SavePlayerStats(ref killerStats); } if (ev.Player.DoNotTrack) { Logger.Debug($"Do Not Track: {ev.Player.Nickname}"); return; } var victimId = ev.Player.UserId; var victimStats = GetPlayerStats(ref victimId); victimStats.player_stats.deaths++; SavePlayerStats(ref victimStats); } private static void OnItemUsed(PlayerUsedItemEventArgs ev) { if (ev.Player.DoNotTrack) { Logger.Debug($"Do Not Track: {ev.Player.Nickname}"); return; } var userId = ev.Player.UserId; var stats = GetPlayerStats(ref userId); var stat = stats.player_items[(int)ev.UsableItem.Type]; stat.item_count++; stat.item_name = ev.UsableItem.Type.ToString(); } } }