C#:
using System;
using System.Collections.Generic;
using System.Linq;
using Newtonsoft.Json;
using Oxide.Core;
using UnityEngine;
namespace Oxide.Plugins
{
[Info("RogueBot Events", "RickySpanish", "1.0.1")]
[Description("Tracks server events for RogueBot integration")]
public class RogueBotEvents : RustPlugin
{
private List<ServerEvent> _events = new List<ServerEvent>();
private const int MAX_EVENTS = 100;
private Timer _perfTimer;
private class ServerEvent
{
public string Type;
public string Event;
public string Description;
public DateTime Timestamp;
public string Location;
public Dictionary<string, string> Details = new Dictionary<string, string>();
}
private Dictionary<string, double> _lastHookTimes = new Dictionary<string, double>();
private List<PluginPerfSnapshot> _latestPerfData = new List<PluginPerfSnapshot>();
private float _lastSnapshotFps = 0;
private class PluginPerfSnapshot
{
public string Name;
public double HookTimeDelta;
public double TotalHookTime;
}
#region Oxide Hooks
private void Init() => LoadData();
private void Unload()
{
_perfTimer?.Destroy();
SaveData();
}
private void OnServerSave() { SaveData(); }
private void OnServerInitialized()
{
_perfTimer = timer.Every(60f, SnapshotPluginPerformance);
}
[ConsoleCommand("roguebotevents")]
private void CmdGetEvents(ConsoleSystem.Arg arg)
{
var activeEvents = GetActiveEvents();
var onlinePlayers = BasePlayer.activePlayerList
.Where(p => p != null && !string.IsNullOrEmpty(p.displayName))
.Select(p => p.displayName).ToList();
int limit = 30;
if (arg.HasArgs()) int.TryParse(arg.GetString(0), out limit);
var output = new
{
activeEvents = activeEvents,
onlinePlayers = onlinePlayers,
recentEvents = _events.Take(limit).Select(e => new
{
type = e.Type,
evt = e.Event,
description = e.Description,
timestamp = e.Timestamp.ToString("yyyy-MM-dd HH:mm:ss"),
minutesAgo = (int)(DateTime.UtcNow - e.Timestamp).TotalMinutes,
location = e.Location,
details = e.Details
})
};
arg.ReplyWith(JsonConvert.SerializeObject(output));
}
private const int MAX_ACTIVE_MINUTES = 120;
private List<object> GetActiveEvents()
{
var started = new Dictionary<string, ServerEvent>();
foreach (var evt in _events.AsEnumerable().Reverse())
{
if (string.IsNullOrEmpty(evt.Event)) continue;
var key = evt.Event + (evt.Location ?? "");
if (evt.Type == "start")
{
started[key] = evt;
}
else if (evt.Type == "end")
{
if (!string.IsNullOrEmpty(evt.Location))
{
started.Remove(key);
}
else
{
var toRemove = started.Keys.Where(k => k.StartsWith(evt.Event)).ToList();
foreach (var k in toRemove)
started.Remove(k);
}
}
}
var now = DateTime.UtcNow;
return started.Values
.Where(e => (now - e.Timestamp).TotalMinutes < MAX_ACTIVE_MINUTES)
.Select(e => new
{
name = e.Event,
description = e.Description,
location = e.Location,
minutesAgo = (int)(now - e.Timestamp).TotalMinutes
}).Cast<object>().ToList();
}
#endregion
#region Event Logging - Simple start/end pattern
private void LogStart(string eventName, string description, string location = null, Dictionary<string, string> details = null)
{
_events.Insert(0, new ServerEvent
{
Type = "start",
Event = eventName,
Description = description,
Location = location,
Timestamp = DateTime.UtcNow,
Details = details ?? new Dictionary<string, string>()
});
TrimEvents();
}
private void LogEnd(string eventName, string description, string location = null, Dictionary<string, string> details = null)
{
_events.Insert(0, new ServerEvent
{
Type = "end",
Event = eventName,
Description = description,
Location = location,
Timestamp = DateTime.UtcNow,
Details = details ?? new Dictionary<string, string>()
});
TrimEvents();
}
private void LogEvent(string type, string description, Dictionary<string, string> details = null)
{
_events.Insert(0, new ServerEvent
{
Type = type,
Description = description,
Timestamp = DateTime.UtcNow,
Details = details ?? new Dictionary<string, string>()
});
TrimEvents();
}
private void TrimEvents()
{
while (_events.Count > MAX_EVENTS)
_events.RemoveAt(_events.Count - 1);
}
#endregion
#region Player Events
private void OnPlayerConnected(BasePlayer player)
{
if (player == null) return;
LogEvent("player_join", $"{player.displayName} joined", new Dictionary<string, string>
{
["player"] = player.displayName
});
}
private void OnPlayerDisconnected(BasePlayer player, string reason)
{
if (player == null) return;
LogEvent("player_leave", $"{player.displayName} left", new Dictionary<string, string>
{
["player"] = player.displayName,
["reason"] = reason ?? "Disconnected"
});
}
private void OnPlayerDeath(BasePlayer player, HitInfo info)
{
if (player == null || player.IsNpc) return;
string killer = "Unknown";
string weapon = "Unknown";
string deathType = "death";
string description;
if (info != null)
{
var attacker = info.InitiatorPlayer;
if (attacker != null && attacker != player)
{
killer = attacker.displayName;
deathType = "pvp_kill";
}
else if (attacker != null && attacker == player)
{
killer = "Self";
deathType = "suicide";
}
else if (info.Initiator != null)
{
killer = info.Initiator.ShortPrefabName ?? "Environment";
deathType = "pve_death";
}
weapon = info.Weapon?.GetItem()?.info?.displayName?.english
?? info.WeaponPrefab?.ShortPrefabName ?? "Unknown";
}
if (deathType == "pvp_kill")
description = $"{killer} killed {player.displayName} with {weapon}";
else if (deathType == "suicide")
description = $"{player.displayName} killed themselves with {weapon}";
else
description = $"{player.displayName} died to {killer}";
LogEvent(deathType, description, new Dictionary<string, string>
{
["victim"] = player.displayName,
["killer"] = killer,
["weapon"] = weapon,
["location"] = GetGrid(player.transform.position)
});
}
#endregion
#region Vanilla Events
private Dictionary<string, DateTime> _lastLogged = new Dictionary<string, DateTime>();
private bool ShouldLog(string eventName)
{
if (_lastLogged.TryGetValue(eventName, out var last) && (DateTime.UtcNow - last).TotalSeconds < 60)
return false;
_lastLogged[eventName] = DateTime.UtcNow;
return true;
}
private void OnEntitySpawned(BaseNetworkable entity)
{
if (entity == null) return;
if (entity is PatrolHelicopter && ShouldLog("heli_start"))
LogStart("Patrol Helicopter", "Patrol Helicopter spawned");
else if (entity is CargoShip && ShouldLog("cargo_start"))
LogStart("Cargo Ship", "Cargo Ship spawned");
else if (entity is CH47Helicopter && ShouldLog("chinook_start"))
LogStart("Chinook", "Chinook spawned");
else if (entity is CargoPlane && ShouldLog("plane_start"))
LogStart("Cargo Plane", "Airdrop incoming");
}
private void OnEntityKill(BaseNetworkable entity)
{
if (entity == null) return;
if (entity is PatrolHelicopter && ShouldLog("heli_end"))
LogEnd("Patrol Helicopter", "Patrol Helicopter destroyed");
else if (entity is CargoShip && ShouldLog("cargo_end"))
LogEnd("Cargo Ship", "Cargo Ship left");
else if (entity is CH47Helicopter && ShouldLog("chinook_end"))
LogEnd("Chinook", "Chinook left");
}
#endregion
#region Custom Plugin Events
void OnConvoyStart() { LogStart("Convoy", "Convoy started"); }
void OnConvoyStop() { LogEnd("Convoy", "Convoy ended"); }
void OnArmoredTrainEventStart() { LogStart("Armored Train", "Armored Train started"); }
void OnArmoredTrainEventStop() { LogEnd("Armored Train", "Armored Train ended"); }
void OnTrainEventStarted(TrainEngine t) { LogStart("Cargo Train", "Cargo Train started"); }
void OnTrainEventEnded(TrainEngine t) { LogEnd("Cargo Train", "Cargo Train ended"); }
void OnHarborEventStart() { LogStart("Harbor Event", "Harbor Event started"); }
void OnHarborEventEnd(HashSet<BaseEntity> e) { LogEnd("Harbor Event", "Harbor Event ended"); }
void OnJunkyardEventStart() { LogStart("Junkyard Event", "Junkyard Event started"); }
void OnJunkyardEventEnd(HashSet<BaseEntity> e) { LogEnd("Junkyard Event", "Junkyard Event ended"); }
void OnPowerPlantEventStart() { LogStart("Power Plant Event", "Power Plant Event started"); }
void OnPowerPlantEventEnd(HashSet<BaseEntity> e) { LogEnd("Power Plant Event", "Power Plant Event ended"); }
void OnSatDishEventStart() { LogStart("Satellite Dish Event", "Satellite Dish Event started"); }
void OnSatDishEventEnd(HashSet<BaseEntity> e) { LogEnd("Satellite Dish Event", "Satellite Dish Event ended"); }
void OnSputnikEventStart() { LogStart("Sputnik Event", "Sputnik Event started"); }
void OnSputnikEventStop() { LogEnd("Sputnik Event", "Sputnik Event ended"); }
void OnWaterEventStart() { LogStart("Water Event", "Water Event started"); }
void OnWaterEventEnd(HashSet<BaseEntity> e) { LogEnd("Water Event", "Water Event ended"); }
void OnAirEventStart(HashSet<BaseEntity> e) { LogStart("Air Event", "Air Event started"); }
void OnAirEventEnd(HashSet<BaseEntity> e) { LogEnd("Air Event", "Air Event ended"); }
void OnGasStationEventStart(Vector3 pos, float radius) { LogStart("Gas Station Event", $"Gas Station Event started at {GetGrid(pos)}", GetGrid(pos)); }
void OnGasStationEventEnd() { LogEnd("Gas Station Event", "Gas Station Event ended"); }
void OnFerryTerminalEventStart(Vector3 pos, float radius) { LogStart("Ferry Terminal Event", "Ferry Terminal Event started"); }
void OnFerryTerminalEventEnd() { LogEnd("Ferry Terminal Event", "Ferry Terminal Event ended"); }
void OnOperationFortressStart() { LogStart("Operation Fortress", "Operation Fortress started"); }
void OnOperationFortressStop() { LogEnd("Operation Fortress", "Operation Fortress ended"); }
void OnCaravanStart() { LogStart("Caravan", "Caravan spawned"); }
void OnCaravanStop() { LogEnd("Caravan", "Caravan ended"); }
void OnCaravanStartMoving(Vector3 position) { LogStart("Caravan Moving", $"Caravan moving at {GetGrid(position)}", GetGrid(position)); }
void OnCaravanStopMoving(Vector3 position) { LogEnd("Caravan Moving", $"Caravan stopped at {GetGrid(position)}"); }
void OnRaidableBaseStarted(Vector3 pos, int diff)
{
string d = diff == 0 ? "Easy" : diff == 1 ? "Medium" : diff == 2 ? "Hard" : "Expert";
string grid = GetGrid(pos);
LogStart($"Raidable Base", $"{d} Raidable Base at {grid}", grid, new Dictionary<string, string> { ["difficulty"] = d });
}
void OnRaidableBaseEnded(Vector3 pos, int diff)
{
string d = diff == 0 ? "Easy" : diff == 1 ? "Medium" : diff == 2 ? "Hard" : "Expert";
string grid = GetGrid(pos);
LogEnd($"Raidable Base", $"{d} Raidable Base completed at {grid}", grid);
}
#endregion
#region Performance Monitoring
private void SnapshotPluginPerformance()
{
try
{
_lastSnapshotFps = Performance.current.frameRate;
var currentPlugins = new HashSet<string>();
var snapshots = new List<PluginPerfSnapshot>();
foreach (var plugin in Interface.Oxide.RootPluginManager.GetPlugins())
{
if (plugin == null) continue;
string name = plugin.Title ?? plugin.Name ?? "Unknown";
currentPlugins.Add(name);
double hookTime = 0;
#if CARBON
try { hookTime = plugin.TotalHookTime.TotalMilliseconds; }
#else
try { hookTime = plugin.TotalHookTime; }
#endif
catch { continue; }
double delta = 0;
if (_lastHookTimes.TryGetValue(name, out var prev))
{
delta = hookTime - prev;
if (delta < 0) delta = hookTime;
}
_lastHookTimes[name] = hookTime;
if (delta > 0.001)
{
snapshots.Add(new PluginPerfSnapshot
{
Name = name,
HookTimeDelta = delta,
TotalHookTime = hookTime
});
}
}
var toRemove = _lastHookTimes.Keys.Where(k => !currentPlugins.Contains(k)).ToList();
foreach (var key in toRemove) _lastHookTimes.Remove(key);
snapshots.Sort((a, b) => b.HookTimeDelta.CompareTo(a.HookTimeDelta));
_latestPerfData = snapshots;
}
catch (Exception ex)
{
Puts($"[Perf] Error: {ex.Message}");
}
}
[ConsoleCommand("roguebotperf")]
private void CmdGetPerf(ConsoleSystem.Arg arg)
{
var top = _latestPerfData.Take(15).Select(p => new
{
name = p.Name,
hookTime60s = Math.Round(p.HookTimeDelta, 4),
totalHookTime = Math.Round(p.TotalHookTime, 2)
});
var output = new
{
fps = Math.Round(_lastSnapshotFps, 1),
entityCount = BaseNetworkable.serverEntities?.Count ?? 0,
memoryMB = Math.Round(System.GC.GetTotalMemory(false) / 1048576.0, 1),
pluginCount = Interface.Oxide.RootPluginManager.GetPlugins().Count(),
topPlugins = top,
timestamp = DateTime.UtcNow.ToString("yyyy-MM-dd HH:mm:ss")
};
arg.ReplyWith(JsonConvert.SerializeObject(output));
}
#endregion
#region Helpers
private string GetGrid(Vector3 pos) => MapHelper.PositionToString(pos);
private void LoadData()
{
_events = Interface.Oxide.DataFileSystem.ReadObject<List<ServerEvent>>("RogueBotEvents") ?? new List<ServerEvent>();
}
private void SaveData()
{
Interface.Oxide.DataFileSystem.WriteObject("RogueBotEvents", _events);
}
#endregion
}
}