idk man
This commit is contained in:
@@ -1,5 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<Weavers xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="FodyWeavers.xsd">
|
||||
<Costura>
|
||||
</Costura>
|
||||
</Weavers>
|
||||
@@ -1,176 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema">
|
||||
<!-- This file was generated by Fody. Manual changes to this file will be lost when your project is rebuilt. -->
|
||||
<xs:element name="Weavers">
|
||||
<xs:complexType>
|
||||
<xs:all>
|
||||
<xs:element name="Costura" minOccurs="0" maxOccurs="1">
|
||||
<xs:complexType>
|
||||
<xs:all>
|
||||
<xs:element minOccurs="0" maxOccurs="1" name="ExcludeAssemblies" type="xs:string">
|
||||
<xs:annotation>
|
||||
<xs:documentation>A list of assembly names to exclude from the default action of "embed all Copy Local references", delimited with line breaks</xs:documentation>
|
||||
</xs:annotation>
|
||||
</xs:element>
|
||||
<xs:element minOccurs="0" maxOccurs="1" name="IncludeAssemblies" type="xs:string">
|
||||
<xs:annotation>
|
||||
<xs:documentation>A list of assembly names to include from the default action of "embed all Copy Local references", delimited with line breaks.</xs:documentation>
|
||||
</xs:annotation>
|
||||
</xs:element>
|
||||
<xs:element minOccurs="0" maxOccurs="1" name="ExcludeRuntimeAssemblies" type="xs:string">
|
||||
<xs:annotation>
|
||||
<xs:documentation>A list of runtime assembly names to exclude from the default action of "embed all Copy Local references", delimited with line breaks</xs:documentation>
|
||||
</xs:annotation>
|
||||
</xs:element>
|
||||
<xs:element minOccurs="0" maxOccurs="1" name="IncludeRuntimeAssemblies" type="xs:string">
|
||||
<xs:annotation>
|
||||
<xs:documentation>A list of runtime assembly names to include from the default action of "embed all Copy Local references", delimited with line breaks.</xs:documentation>
|
||||
</xs:annotation>
|
||||
</xs:element>
|
||||
<xs:element minOccurs="0" maxOccurs="1" name="Unmanaged32Assemblies" type="xs:string">
|
||||
<xs:annotation>
|
||||
<xs:documentation>Obsolete, use UnmanagedWinX86Assemblies instead</xs:documentation>
|
||||
</xs:annotation>
|
||||
</xs:element>
|
||||
<xs:element minOccurs="0" maxOccurs="1" name="UnmanagedWinX86Assemblies" type="xs:string">
|
||||
<xs:annotation>
|
||||
<xs:documentation>A list of unmanaged X86 (32 bit) assembly names to include, delimited with line breaks.</xs:documentation>
|
||||
</xs:annotation>
|
||||
</xs:element>
|
||||
<xs:element minOccurs="0" maxOccurs="1" name="Unmanaged64Assemblies" type="xs:string">
|
||||
<xs:annotation>
|
||||
<xs:documentation>Obsolete, use UnmanagedWinX64Assemblies instead.</xs:documentation>
|
||||
</xs:annotation>
|
||||
</xs:element>
|
||||
<xs:element minOccurs="0" maxOccurs="1" name="UnmanagedWinX64Assemblies" type="xs:string">
|
||||
<xs:annotation>
|
||||
<xs:documentation>A list of unmanaged X64 (64 bit) assembly names to include, delimited with line breaks.</xs:documentation>
|
||||
</xs:annotation>
|
||||
</xs:element>
|
||||
<xs:element minOccurs="0" maxOccurs="1" name="UnmanagedWinArm64Assemblies" type="xs:string">
|
||||
<xs:annotation>
|
||||
<xs:documentation>A list of unmanaged Arm64 (64 bit) assembly names to include, delimited with line breaks.</xs:documentation>
|
||||
</xs:annotation>
|
||||
</xs:element>
|
||||
<xs:element minOccurs="0" maxOccurs="1" name="PreloadOrder" type="xs:string">
|
||||
<xs:annotation>
|
||||
<xs:documentation>The order of preloaded assemblies, delimited with line breaks.</xs:documentation>
|
||||
</xs:annotation>
|
||||
</xs:element>
|
||||
</xs:all>
|
||||
<xs:attribute name="CreateTemporaryAssemblies" type="xs:boolean">
|
||||
<xs:annotation>
|
||||
<xs:documentation>This will copy embedded files to disk before loading them into memory. This is helpful for some scenarios that expected an assembly to be loaded from a physical file.</xs:documentation>
|
||||
</xs:annotation>
|
||||
</xs:attribute>
|
||||
<xs:attribute name="IncludeDebugSymbols" type="xs:boolean">
|
||||
<xs:annotation>
|
||||
<xs:documentation>Controls if .pdbs for reference assemblies are also embedded.</xs:documentation>
|
||||
</xs:annotation>
|
||||
</xs:attribute>
|
||||
<xs:attribute name="IncludeRuntimeReferences" type="xs:boolean">
|
||||
<xs:annotation>
|
||||
<xs:documentation>Controls if runtime assemblies are also embedded.</xs:documentation>
|
||||
</xs:annotation>
|
||||
</xs:attribute>
|
||||
<xs:attribute name="UseRuntimeReferencePaths" type="xs:boolean">
|
||||
<xs:annotation>
|
||||
<xs:documentation>Controls whether the runtime assemblies are embedded with their full path or only with their assembly name.</xs:documentation>
|
||||
</xs:annotation>
|
||||
</xs:attribute>
|
||||
<xs:attribute name="DisableCompression" type="xs:boolean">
|
||||
<xs:annotation>
|
||||
<xs:documentation>Embedded assemblies are compressed by default, and uncompressed when they are loaded. You can turn compression off with this option.</xs:documentation>
|
||||
</xs:annotation>
|
||||
</xs:attribute>
|
||||
<xs:attribute name="DisableCleanup" type="xs:boolean">
|
||||
<xs:annotation>
|
||||
<xs:documentation>As part of Costura, embedded assemblies are no longer included as part of the build. This cleanup can be turned off.</xs:documentation>
|
||||
</xs:annotation>
|
||||
</xs:attribute>
|
||||
<xs:attribute name="DisableEventSubscription" type="xs:boolean">
|
||||
<xs:annotation>
|
||||
<xs:documentation>The attach method no longer subscribes to the `AppDomain.AssemblyResolve` (.NET 4.x) and `AssemblyLoadContext.Resolving` (.NET 6.0+) events.</xs:documentation>
|
||||
</xs:annotation>
|
||||
</xs:attribute>
|
||||
<xs:attribute name="LoadAtModuleInit" type="xs:boolean">
|
||||
<xs:annotation>
|
||||
<xs:documentation>Costura by default will load as part of the module initialization. This flag disables that behavior. Make sure you call CosturaUtility.Initialize() somewhere in your code.</xs:documentation>
|
||||
</xs:annotation>
|
||||
</xs:attribute>
|
||||
<xs:attribute name="IgnoreSatelliteAssemblies" type="xs:boolean">
|
||||
<xs:annotation>
|
||||
<xs:documentation>Costura will by default use assemblies with a name like 'resources.dll' as a satellite resource and prepend the output path. This flag disables that behavior.</xs:documentation>
|
||||
</xs:annotation>
|
||||
</xs:attribute>
|
||||
<xs:attribute name="ExcludeAssemblies" type="xs:string">
|
||||
<xs:annotation>
|
||||
<xs:documentation>A list of assembly names to exclude from the default action of "embed all Copy Local references", delimited with |</xs:documentation>
|
||||
</xs:annotation>
|
||||
</xs:attribute>
|
||||
<xs:attribute name="IncludeAssemblies" type="xs:string">
|
||||
<xs:annotation>
|
||||
<xs:documentation>A list of assembly names to include from the default action of "embed all Copy Local references", delimited with |.</xs:documentation>
|
||||
</xs:annotation>
|
||||
</xs:attribute>
|
||||
<xs:attribute name="ExcludeRuntimeAssemblies" type="xs:string">
|
||||
<xs:annotation>
|
||||
<xs:documentation>A list of runtime assembly names to exclude from the default action of "embed all Copy Local references", delimited with |</xs:documentation>
|
||||
</xs:annotation>
|
||||
</xs:attribute>
|
||||
<xs:attribute name="IncludeRuntimeAssemblies" type="xs:string">
|
||||
<xs:annotation>
|
||||
<xs:documentation>A list of runtime assembly names to include from the default action of "embed all Copy Local references", delimited with |.</xs:documentation>
|
||||
</xs:annotation>
|
||||
</xs:attribute>
|
||||
<xs:attribute name="Unmanaged32Assemblies" type="xs:string">
|
||||
<xs:annotation>
|
||||
<xs:documentation>Obsolete, use UnmanagedWinX86Assemblies instead</xs:documentation>
|
||||
</xs:annotation>
|
||||
</xs:attribute>
|
||||
<xs:attribute name="UnmanagedWinX86Assemblies" type="xs:string">
|
||||
<xs:annotation>
|
||||
<xs:documentation>A list of unmanaged X86 (32 bit) assembly names to include, delimited with |.</xs:documentation>
|
||||
</xs:annotation>
|
||||
</xs:attribute>
|
||||
<xs:attribute name="Unmanaged64Assemblies" type="xs:string">
|
||||
<xs:annotation>
|
||||
<xs:documentation>Obsolete, use UnmanagedWinX64Assemblies instead</xs:documentation>
|
||||
</xs:annotation>
|
||||
</xs:attribute>
|
||||
<xs:attribute name="UnmanagedWinX64Assemblies" type="xs:string">
|
||||
<xs:annotation>
|
||||
<xs:documentation>A list of unmanaged X64 (64 bit) assembly names to include, delimited with |.</xs:documentation>
|
||||
</xs:annotation>
|
||||
</xs:attribute>
|
||||
<xs:attribute name="UnmanagedWinArm64Assemblies" type="xs:string">
|
||||
<xs:annotation>
|
||||
<xs:documentation>A list of unmanaged Arm64 (64 bit) assembly names to include, delimited with |.</xs:documentation>
|
||||
</xs:annotation>
|
||||
</xs:attribute>
|
||||
<xs:attribute name="PreloadOrder" type="xs:string">
|
||||
<xs:annotation>
|
||||
<xs:documentation>The order of preloaded assemblies, delimited with |.</xs:documentation>
|
||||
</xs:annotation>
|
||||
</xs:attribute>
|
||||
</xs:complexType>
|
||||
</xs:element>
|
||||
</xs:all>
|
||||
<xs:attribute name="VerifyAssembly" type="xs:boolean">
|
||||
<xs:annotation>
|
||||
<xs:documentation>'true' to run assembly verification (PEVerify) on the target assembly after all weavers have been executed.</xs:documentation>
|
||||
</xs:annotation>
|
||||
</xs:attribute>
|
||||
<xs:attribute name="VerifyIgnoreCodes" type="xs:string">
|
||||
<xs:annotation>
|
||||
<xs:documentation>A comma-separated list of error codes that can be safely ignored in assembly verification.</xs:documentation>
|
||||
</xs:annotation>
|
||||
</xs:attribute>
|
||||
<xs:attribute name="GenerateXsd" type="xs:boolean">
|
||||
<xs:annotation>
|
||||
<xs:documentation>'false' to turn off automatic generation of the XML Schema file.</xs:documentation>
|
||||
</xs:annotation>
|
||||
</xs:attribute>
|
||||
</xs:complexType>
|
||||
</xs:element>
|
||||
</xs:schema>
|
||||
Generated
+97
@@ -0,0 +1,97 @@
|
||||
# This file is automatically @generated by Cargo.
|
||||
# It is not intended for manual editing.
|
||||
version = 4
|
||||
|
||||
[[package]]
|
||||
name = "bincode"
|
||||
version = "2.0.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "36eaf5d7b090263e8150820482d5d93cd964a81e4019913c972f4edcc6edb740"
|
||||
dependencies = [
|
||||
"bincode_derive",
|
||||
"serde",
|
||||
"unty",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "bincode_derive"
|
||||
version = "2.0.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bf95709a440f45e986983918d0e8a1f30a9b1df04918fc828670606804ac3c09"
|
||||
dependencies = [
|
||||
"virtue",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "proc-macro2"
|
||||
version = "1.0.95"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "02b3e5e68a3a1a02aad3ec490a98007cbc13c37cbe84a3cd7b8e406d76e7f778"
|
||||
dependencies = [
|
||||
"unicode-ident",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "quote"
|
||||
version = "1.0.40"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1885c039570dc00dcb4ff087a89e185fd56bae234ddc7f056a945bf36467248d"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde"
|
||||
version = "1.0.219"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5f0e2c6ed6606019b4e29e69dbaba95b11854410e5347d525002456dbbb786b6"
|
||||
dependencies = [
|
||||
"serde_derive",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde_derive"
|
||||
version = "1.0.219"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5b0276cf7f2c73365f7157c8123c21cd9a50fbbd844757af28ca1f5925fc2a00"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "stats_tracker"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"bincode",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "syn"
|
||||
version = "2.0.101"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8ce2b7fc941b3a24138a0a7cf8e858bfc6a992e7978a068a5c760deb0ed43caf"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"unicode-ident",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "unicode-ident"
|
||||
version = "1.0.18"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5a5f39404a5da50712a4c1eecf25e90dd62b613502b7e925fd4e4d19b5c96512"
|
||||
|
||||
[[package]]
|
||||
name = "unty"
|
||||
version = "0.0.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6d49784317cd0d1ee7ec5c716dd598ec5b4483ea832a2dced265471cc0f690ae"
|
||||
|
||||
[[package]]
|
||||
name = "virtue"
|
||||
version = "0.0.18"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "051eb1abcf10076295e815102942cc58f9d5e3b4560e46e53c21e8ff6f3af7b1"
|
||||
@@ -0,0 +1,18 @@
|
||||
[package]
|
||||
name = "stats_tracker"
|
||||
version = "0.1.0"
|
||||
edition = "2024"
|
||||
|
||||
[dependencies]
|
||||
bincode = "2.0"
|
||||
|
||||
[lib]
|
||||
crate-type = ["cdylib"]
|
||||
name = "stats_tracker"
|
||||
|
||||
[profile.release]
|
||||
opt-level = "z"
|
||||
codegen-units = 1
|
||||
lto = "fat"
|
||||
overflow-checks = false
|
||||
panic = "abort"
|
||||
@@ -0,0 +1,98 @@
|
||||
use std::{
|
||||
ffi::{CStr, c_char},
|
||||
fs::File,
|
||||
};
|
||||
|
||||
use bincode::{Decode, Encode};
|
||||
|
||||
#[derive(Decode, Encode, Clone)]
|
||||
#[repr(C)]
|
||||
pub struct PlayerStat {
|
||||
kills: u32,
|
||||
deaths: u32,
|
||||
team_damage: u32,
|
||||
}
|
||||
|
||||
#[derive(Decode, Encode, Clone, Default)]
|
||||
#[repr(C)]
|
||||
pub struct ItemStat {
|
||||
item_name: String,
|
||||
item_count: u32,
|
||||
}
|
||||
|
||||
#[derive(Decode, Encode, Clone)]
|
||||
#[repr(C)]
|
||||
pub struct Player {
|
||||
player_id: String,
|
||||
player_stats: PlayerStat,
|
||||
player_items: [ItemStat; 256],
|
||||
}
|
||||
|
||||
impl Default for Player {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
player_id: String::new(),
|
||||
player_stats: PlayerStat {
|
||||
kills: 0,
|
||||
deaths: 0,
|
||||
team_damage: 0,
|
||||
},
|
||||
player_items: std::array::from_fn(|_| ItemStat::default()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[unsafe(no_mangle)]
|
||||
/// # Safety
|
||||
/// `player_id` must be a valid, null-terminated C string pointer.
|
||||
pub unsafe extern "C" fn get_player_stats(player_id: *const c_char) -> *const Player {
|
||||
let player_id = unsafe { CStr::from_ptr(player_id) }
|
||||
.to_string_lossy()
|
||||
.into_owned();
|
||||
|
||||
let db_location = "./stats.data";
|
||||
|
||||
let player_stats: Vec<Player> = match File::open(db_location) {
|
||||
Ok(mut data) => bincode::decode_from_std_read(&mut data, bincode::config::standard())
|
||||
.unwrap_or_default(),
|
||||
Err(_) => Vec::new(),
|
||||
};
|
||||
|
||||
player_stats
|
||||
.iter()
|
||||
.find(|p| p.player_id == player_id)
|
||||
.unwrap_or(&Player::default())
|
||||
}
|
||||
|
||||
#[unsafe(no_mangle)]
|
||||
/// # Safety
|
||||
/// `player` must be a valid pointer to a `Player` struct.
|
||||
pub unsafe extern "C" fn save_player_stats(player: *const Player) -> bool {
|
||||
let player = unsafe { &*player };
|
||||
let db_location = "./stats.data";
|
||||
|
||||
let mut player_stats: Vec<Player> = match File::open(db_location) {
|
||||
Ok(mut data) => bincode::decode_from_std_read(&mut data, bincode::config::standard())
|
||||
.unwrap_or_default(),
|
||||
Err(_) => Vec::new(),
|
||||
};
|
||||
|
||||
// Find and update existing player or add new player
|
||||
if let Some(existing_player) = player_stats
|
||||
.iter_mut()
|
||||
.find(|p| p.player_id == player.player_id)
|
||||
{
|
||||
*existing_player = player.clone();
|
||||
} else {
|
||||
player_stats.push(player.clone());
|
||||
}
|
||||
|
||||
// Save updated stats
|
||||
match File::create(db_location) {
|
||||
Ok(mut file) => {
|
||||
bincode::encode_into_std_write(&player_stats, &mut file, bincode::config::standard())
|
||||
.is_ok()
|
||||
}
|
||||
Err(_) => false,
|
||||
}
|
||||
}
|
||||
+183
-116
@@ -1,13 +1,40 @@
|
||||
using LabApi.Features;
|
||||
using LabApi.Loader.Features.Plugins;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Data.SQLite;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Reflection;
|
||||
using System.Runtime.InteropServices;
|
||||
using LabApi.Events.Arguments.PlayerEvents;
|
||||
using LabApi.Events.Handlers;
|
||||
using LabApi.Loader;
|
||||
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";
|
||||
@@ -15,148 +42,188 @@ namespace StatsTracker
|
||||
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 string _dbPath;
|
||||
private readonly ConcurrentDictionary<string, PlayerStats> _currentSessionStats = new();
|
||||
|
||||
private class PlayerStats
|
||||
{
|
||||
public int Kills { get; set; }
|
||||
public int Deaths { get; set; }
|
||||
public Dictionary<ItemType, int> ItemUsage { get; set; } = new();
|
||||
}
|
||||
|
||||
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()
|
||||
{
|
||||
_dbPath = Path.Combine(this.GetConfigDirectory().FullName, "stats.db");
|
||||
InitializeDatabase();
|
||||
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.Left += OnPlayerLeft;
|
||||
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;
|
||||
PlayerEvents.Left -= OnPlayerLeft;
|
||||
|
||||
// Save any remaining stats
|
||||
foreach (var player in _currentSessionStats)
|
||||
{
|
||||
SavePlayerStats(player.Key, player.Value);
|
||||
}
|
||||
_currentSessionStats.Clear();
|
||||
}
|
||||
|
||||
private void InitializeDatabase()
|
||||
private static void OnHurt(PlayerHurtEventArgs ev)
|
||||
{
|
||||
using var connection = new SQLiteConnection($"Data Source={_dbPath}");
|
||||
connection.Open();
|
||||
|
||||
using var command = connection.CreateCommand();
|
||||
command.CommandText = """
|
||||
|
||||
CREATE TABLE IF NOT EXISTS PlayerStats (
|
||||
UserId TEXT PRIMARY KEY,
|
||||
Kills INTEGER DEFAULT 0,
|
||||
Deaths INTEGER DEFAULT 0
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS ItemUsage (
|
||||
UserId TEXT,
|
||||
ItemType INTEGER,
|
||||
UsageCount INTEGER DEFAULT 0,
|
||||
PRIMARY KEY (UserId, ItemType)
|
||||
);
|
||||
""";
|
||||
command.ExecuteNonQuery();
|
||||
}
|
||||
|
||||
private void OnPlayerDied(PlayerDeathEventArgs ev)
|
||||
{
|
||||
if (ev.Attacker != null)
|
||||
switch (ev.Attacker)
|
||||
{
|
||||
var killerStats = _currentSessionStats.GetOrAdd(ev.Attacker.UserId, _ => new PlayerStats());
|
||||
killerStats.Kills++;
|
||||
case null:
|
||||
case { DoNotTrack: true }:
|
||||
return;
|
||||
}
|
||||
|
||||
var victimStats = _currentSessionStats.GetOrAdd(ev.Player.UserId, _ => new PlayerStats());
|
||||
victimStats.Deaths++;
|
||||
}
|
||||
|
||||
private void OnItemUsed(PlayerUsedItemEventArgs ev)
|
||||
{
|
||||
var stats = _currentSessionStats.GetOrAdd(ev.Player.UserId, _ => new PlayerStats());
|
||||
if(ev.Attacker.Team != ev.Player.Team) return;
|
||||
|
||||
if (!stats.ItemUsage.ContainsKey(ev.UsableItem.Type))
|
||||
stats.ItemUsage[ev.UsableItem.Type] = 0;
|
||||
|
||||
stats.ItemUsage[ev.UsableItem.Type]++;
|
||||
var userId = ev.Attacker.UserId;
|
||||
var killerStats = GetPlayerStats(ref userId);
|
||||
killerStats.player_stats.team_damage++;
|
||||
SavePlayerStats(ref killerStats);
|
||||
}
|
||||
|
||||
private void OnPlayerLeft(PlayerLeftEventArgs ev)
|
||||
private static void OnPlayerDied(PlayerDeathEventArgs ev)
|
||||
{
|
||||
if (_currentSessionStats.TryRemove(ev.Player.UserId, out var stats))
|
||||
if (ev.Attacker != null && ev.Attacker.Nickname != "emeraldo" && !ev.Attacker.DoNotTrack)
|
||||
{
|
||||
SavePlayerStats(ev.Player.UserId, stats);
|
||||
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 void SavePlayerStats(string userId, PlayerStats stats)
|
||||
private static void OnItemUsed(PlayerUsedItemEventArgs ev)
|
||||
{
|
||||
using var connection = new SQLiteConnection($"Data Source={_dbPath}");
|
||||
connection.Open();
|
||||
using var transaction = connection.BeginTransaction();
|
||||
|
||||
try
|
||||
if (ev.Player.DoNotTrack)
|
||||
{
|
||||
// Update player stats
|
||||
using (var command = connection.CreateCommand())
|
||||
{
|
||||
command.CommandText = """
|
||||
|
||||
INSERT INTO PlayerStats (UserId, Kills, Deaths)
|
||||
VALUES (@userId, @kills, @deaths)
|
||||
ON CONFLICT(UserId) DO UPDATE SET
|
||||
Kills = Kills + @kills,
|
||||
Deaths = Deaths + @deaths;
|
||||
""";
|
||||
|
||||
command.Parameters.AddWithValue("@userId", userId);
|
||||
command.Parameters.AddWithValue("@kills", stats.Kills);
|
||||
command.Parameters.AddWithValue("@deaths", stats.Deaths);
|
||||
command.ExecuteNonQuery();
|
||||
}
|
||||
|
||||
// Update item usage
|
||||
foreach (var itemInfoPair in stats.ItemUsage)
|
||||
{
|
||||
var itemType = itemInfoPair.Key;
|
||||
var count = itemInfoPair.Value;
|
||||
using var command = connection.CreateCommand();
|
||||
command.CommandText = """
|
||||
|
||||
INSERT INTO ItemUsage (UserId, ItemType, UsageCount)
|
||||
VALUES (@userId, @itemType, @count)
|
||||
ON CONFLICT(UserId, ItemType) DO UPDATE SET
|
||||
UsageCount = UsageCount + @count;
|
||||
""";
|
||||
|
||||
command.Parameters.AddWithValue("@userId", userId);
|
||||
command.Parameters.AddWithValue("@itemType", (int)itemType);
|
||||
command.Parameters.AddWithValue("@count", count);
|
||||
command.ExecuteNonQuery();
|
||||
}
|
||||
|
||||
transaction.Commit();
|
||||
}
|
||||
catch
|
||||
{
|
||||
transaction.Rollback();
|
||||
throw;
|
||||
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();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -5,6 +5,7 @@
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>disable</Nullable>
|
||||
<LangVersion>latest</LangVersion>
|
||||
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
|
||||
</PropertyGroup>
|
||||
|
||||
<PropertyGroup Condition=" '$(Configuration)' == 'Debug' ">
|
||||
@@ -21,14 +22,18 @@
|
||||
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Costura.Fody" Version="6.0.0">
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
</PackageReference>
|
||||
<EmbeddedResource Include="Rust/target/x86_64-pc-windows-gnu/release/stats_tracker.dll" />
|
||||
<EmbeddedResource Include="Rust/target/x86_64-unknown-linux-gnu/release/libstats_tracker.so" />
|
||||
|
||||
<PackageReference Include="Northwood.LabAPI" Version="1.0.2"/>
|
||||
<PackageReference Include="System.Data.SQLite" Version="1.0.119" />
|
||||
</ItemGroup>
|
||||
|
||||
<Target Name="RustBuild" BeforeTargets="PrepareForBuild">
|
||||
<Exec Command="echo 'Configuration: $(Configuration)'"/>
|
||||
<Exec Command="cargo build -r --target x86_64-pc-windows-gnu" WorkingDirectory="./Rust"/>
|
||||
<Exec Command="cargo build -r --target x86_64-unknown-linux-gnu" WorkingDirectory="./Rust"/>
|
||||
</Target>
|
||||
|
||||
|
||||
<ItemGroup>
|
||||
<Reference Include="0Harmony">
|
||||
@@ -59,4 +64,4 @@
|
||||
<HintPath>..\dependencies\UnityEngine.CoreModule.dll</HintPath>
|
||||
</Reference>
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
</Project>
|
||||
Reference in New Issue
Block a user