C#:
// Все права принадлежат дискорд сообществу https://discord.gg/VgNHPpNrz6
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using Oxide.Core;
using Oxide.Core.Libraries.Covalence;
using Oxide.Core.Plugins;
using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using System.Text;
using UnityEngine;
namespace Oxide.Plugins {
[Info("Raid Tracker", "Clearshot", "2.1.1")]
[Description("Track raids by explosives, weapons, and ammo with detailed on-screen visuals")]
class RaidTracker : CovalencePlugin {
private bool _dev = false;
// force certain config options
private bool _debug = false;
// respect config, detailed output
private float _debugDrawDuration = 15f;
private static RaidTracker _instance;
private PluginConfig _config;
private bool _isConfigValid = true;
private StringBuilder _sb = new StringBuilder();
private Game.Rust.Libraries.Player _rustPlayer = Interface.Oxide.GetLibrary<Game.Rust.Libraries.Player>("Player");
private DiscordWebhookManager _discordWebhookManager = new DiscordWebhookManager();
private readonly int _collisionLayerMask = LayerMask.GetMask("Construction", "Deployed");
private Dictionary<Vector3, ulong> _MLRSRocketOwners = new Dictionary<Vector3, ulong>();
private Dictionary<ulong, bool> _verboseMode = new Dictionary<ulong, bool>();
private Dictionary<ulong, string[]> _lastViewCommand = new Dictionary<ulong, string[]>();
private Dictionary<string, DecayEntityIgnoreOptions> _decayEntityIgnoreList = new Dictionary<string, DecayEntityIgnoreOptions>();
private Dictionary<string, string> _prefabToItem = new Dictionary<string, string>();
private Dictionary<string, string> _buildingBlockPrettyNames = new Dictionary<string, string>();
private Dictionary<ulong, float> _notificationCooldown = new Dictionary<ulong, float>();
private bool _wipeData;
private List<RaidEvent> _raidEventLog = new List<RaidEvent>();
private string _raidEventLogFilename;
private int _raidEventLogCount;
private string[] _ignoredTimedExplosives = new string[] {
"firecrackers.deployed", "flare.deployed", "maincannonshell", "rocket_heli", "rocket_heli_napalm"
}
;
private string[] _uniqueHexColors = new string[] {
"#01FFFE", "#FFA6FE", "#FFDB66", "#006401", "#010067", "#95003A", "#007DB5", "#FF00F6", "#FFEEE8", "#774D00", "#90FB92", "#0076FF", "#D5FF00", "#FF937E", "#6A826C", "#FF029D", "#FE8900", "#7A4782", "#7E2DD2", "#85A900", "#FF0056", "#A42400", "#00AE7E", "#683D3B", "#BDC6FF", "#263400", "#BDD393", "#00B917", "#9E008E", "#001544", "#C28C9F", "#FF74A3", "#01D0FF", "#004754", "#E56FFE", "#788231", "#0E4CA1", "#91D0CB", "#BE9970", "#968AE8", "#BB8800", "#43002C", "#DEFF74", "#00FFC6", "#FFE502", "#620E00", "#008F9C", "#98FF52", "#7544B1", "#B500FF", "#00FF78", "#FF6E41", "#005F39", "#6B6882", "#5FAD4E", "#A75740", "#A5FFD2", "#FFB167", "#009BFF", "#E85EBE", "#00FF00", "#0000FF", "#FF0000", "#000000"
}
;
private List<Color> _uniqueColors = new List<Color>();
private Dictionary<ulong, Color> _teamColors = new Dictionary<ulong, Color>();
private int _currentTeamColorIdx = 0;
[PluginReference]
Plugin AbandonedBases, Clans, RaidableBases;
private const string PERM_WIPE = "raidtracker.wipe";
private const string PERM_PX = "raidtracker.px";
#region Hooks
private void Init() {
permission.RegisterPermission(PERM_WIPE, this);
permission.RegisterPermission(PERM_PX, this);
Unsubscribe(nameof(OnPlayerDisconnected));
Unsubscribe(nameof(OnMlrsFired));
Unsubscribe(nameof(OnMlrsFiringEnded));
Unsubscribe(nameof(OnEntitySpawned));
Unsubscribe(nameof(OnEntityDeath));
Unsubscribe(nameof(OnServerSave));
}
private void OnServerInitialized() {
_debug = _debug || _config.debug;
if (_dev) {
_debug = true;
Puts("[DEV] Dev mode enabled!");
}
PrintDebug("Debug mode enabled!");
_raidEventLogFilename = $"{Name}\\RaidEventLog";
_raidEventLog = Interface.Oxide.DataFileSystem.ReadObject<List<RaidEvent>>(_raidEventLogFilename);
_raidEventLogCount = _raidEventLog.Count;
foreach (string hexColor in _uniqueHexColors) {
Color color;
if (ColorUtility.TryParseHtmlString(hexColor, out color)) _uniqueColors.Add(color);
}
foreach (var item in ItemManager.itemList.OrderBy(x => x.shortname)) {
ItemModEntity itemModEnt = item.GetComponent<ItemModEntity>();
if (itemModEnt != null) {
var gameObjRef = itemModEnt.entityPrefab;
if (string.IsNullOrEmpty(gameObjRef.guid)) continue;
AddPrefabToItem(gameObjRef.resourcePath, item.shortname, nameof(ItemModEntity));
}
ItemModDeployable itemModDeploy = item.GetComponent<ItemModDeployable>();
if (itemModDeploy != null) {
var gameObjRef = itemModDeploy.entityPrefab;
if (string.IsNullOrEmpty(gameObjRef.guid)) continue;
AddPrefabToItem(gameObjRef.resourcePath, item.shortname, nameof(ItemModDeployable));
}
ItemModProjectile itemModProj = item.GetComponent<ItemModProjectile>();
if (itemModProj != null) {
var gameObjRef = itemModProj.projectileObject;
if (string.IsNullOrEmpty(gameObjRef.guid)) continue;
AddPrefabToItem(gameObjRef.resourcePath, item.shortname, nameof(ItemModProjectile));
}
}
if (_wipeData && _config.deleteDataOnWipe) {
Puts($"Wipe detected! Removing {_raidEventLog.Count} raid events.");
_wipeData = false;
_raidEventLog = new List<RaidEvent>();
SaveRaidEventLog();
}
if (_raidEventLog.Count > 0) {
int removed = 0;
DateTime currentDateTime = DateTime.Now;
for (int i = _raidEventLog.Count - 1; i >= 0; i--) {
RaidEvent raidEvent = _raidEventLog[i];
if (currentDateTime.Subtract(raidEvent.timestamp).TotalDays >= _config.daysBeforeDelete) {
_raidEventLog.RemoveAt(i);
removed++;
}
}
if (removed > 0) {
Puts($"Removed {removed} raid events older than {_config.daysBeforeDelete} days");
SaveRaidEventLog();
}
}
var saveList = false;
var decayEntityListFilename = $"{Name}\\DecayEntityIgnoreList";
if (!Interface.Oxide.DataFileSystem.ExistsDatafile(decayEntityListFilename)) {
Puts($"Generating DecayEntityIgnoreList, any items enabled in this list will be ignored by the plugin");
Puts($"Saving DecayEntityIgnoreList to /oxide/data/{Utility.CleanPath(decayEntityListFilename)}.json");
}
_decayEntityIgnoreList = Interface.Oxide.DataFileSystem.ReadObject<Dictionary<string, DecayEntityIgnoreOptions>>(decayEntityListFilename);
Dictionary<BuildingGrade.Enum, string> buildingGradeNames = new Dictionary<BuildingGrade.Enum, string> {
{
BuildingGrade.Enum.Twigs, "Twig"
}
, {
BuildingGrade.Enum.TopTier, "HQM"
}
}
;
foreach (var prefab in GameManifest.Current.entities) {
var gameObj = GameManager.server.FindPrefab(prefab);
if (gameObj == null) continue;
var thrownWep = gameObj.GetComponent<ThrownWeapon>();
if (thrownWep != null) {
var itemShortname = GetItemFromPrefabShortname(thrownWep.ShortPrefabName);
// get item shortname from held entity
AddPrefabToItem(thrownWep.prefabToThrow.resourcePath, itemShortname, nameof(ThrownWeapon));
// assign deployed entity to same item shortname
PrintDebug($" {thrownWep.ShortPrefabName}[{thrownWep.GetType()}] -> {GetPrefabShortname(thrownWep.prefabToThrow.resourcePath)} -> {itemShortname}");
}
var ent = gameObj.GetComponent<DecayEntity>();
if (ent != null) {
var itemShortname = GetItemFromPrefabShortname(ent.ShortPrefabName);
if (!_decayEntityIgnoreList.ContainsKey(ent.PrefabName)) {
var item = ItemManager.FindItemDefinition(itemShortname);
var itemName = item != null ? $"{item?.displayName?.english ?? "Unknown"} ({itemShortname})" : "";
_decayEntityIgnoreList[ent.PrefabName] = new DecayEntityIgnoreOptions {
name = itemName, ignore = false, ignoreDiscord = false
}
;
LogToSingleFile("decay_entity_log", $"added {ent.PrefabName} {(!string.IsNullOrEmpty(itemName) ? $"[ITEM : {
itemName
}
]" : "")}");
saveList = true;
}
if (ent is BuildingBlock) {
PrintDebug($"BuildingBlock - {ent.ShortPrefabName}");
Type gradeType = typeof(BuildingGrade.Enum);
foreach(var grade in Enum.GetNames(gradeType)) {
var e = (BuildingGrade.Enum)Enum.Parse(gradeType, grade);
if (e == BuildingGrade.Enum.None || e == BuildingGrade.Enum.Count) continue;
var buildingBlockItemShortname = $"{ent.ShortPrefabName}.{grade.ToLower()}";
var gradeName = buildingGradeNames.ContainsKey(e) ? buildingGradeNames[e] : grade;
var prettyName = $"{gradeName} {ent.prefabAttribute.Find<Construction>(StringPool.Get(ent.PrefabName))?.info.name?.english ?? "Unknown"}";
_buildingBlockPrettyNames[buildingBlockItemShortname] = prettyName;
PrintDebug($" BuildingGrade[{grade}] - {prettyName}[{buildingBlockItemShortname}]");
}
}
}
}
if (saveList) {
var sorted = _decayEntityIgnoreList.OrderBy(x => string.IsNullOrWhiteSpace(x.Value.name)).ThenBy(x => x.Value.name).ToDictionary(x => x.Key, x => x.Value);
Interface.Oxide.DataFileSystem.WriteObject(decayEntityListFilename, sorted);
}
var saveCfg = false;
foreach (var trackerList in _config.trackers) {
foreach (var weaponCfg in trackerList.Value) {
if (!string.IsNullOrEmpty(weaponCfg.Value.name)) continue;
var shortname = GetItemFromPrefabShortname(weaponCfg.Key);
weaponCfg.Value.name = GetPrettyItemName(shortname);
saveCfg = true;
}
}
// Don't overwrite the config if invalid since the user will lose their config!
if (_isConfigValid && saveCfg) SaveConfig();
Subscribe(nameof(OnPlayerDisconnected));
Subscribe(nameof(OnMlrsFired));
Subscribe(nameof(OnMlrsFiringEnded));
Subscribe(nameof(OnEntitySpawned));
Subscribe(nameof(OnEntityDeath));
Subscribe(nameof(OnServerSave));
}
private void Unload() {
var trackers = UnityEngine.Object.FindObjectsOfType<ExplosiveTracker>();
if (trackers != null && trackers.Length > 0) {
Puts($"Destroying {trackers.Length} explosive trackers");
foreach (var t in trackers) {
t.logEvent = false;
UnityEngine.Object.Destroy(t);
}
}
_instance = null;
SaveRaidEventLog();
}
private void OnPlayerDisconnected(BasePlayer pl, string reason) {
_verboseMode.Remove(pl.userID);
_lastViewCommand.Remove(pl.userID);
_notificationCooldown.Remove(pl.userID);
}
private void OnMlrsFired(MLRS mlrs, BasePlayer driver) {
_MLRSRocketOwners[mlrs.transform.position] = driver.userID;
}
private void OnMlrsFiringEnded(MLRS mlrs) {
_MLRSRocketOwners.Remove(mlrs.transform.position);
}
private void OnEntitySpawned(TimedExplosive ent) {
if (ent == null || _ignoredTimedExplosives.Contains(ent.ShortPrefabName)) return;
var trackerCategory = "entity_collision";
if (!AddOrFindWeaponConfig(trackerCategory, ent.ShortPrefabName).enabled) return;
if (ent is MLRSRocket && _MLRSRocketOwners.Count > 0) // fix MLRS rocket creatorEntity being null when there is no driver in the MLRS truck
{
var mlrsOwnerID = _MLRSRocketOwners.First(x => Vector3.Distance(x.Key, ent.transform.position) < 25f).Value;
var mlrsOwner = BasePlayer.FindByID(mlrsOwnerID);
if (mlrsOwner != null) {
ent.creatorEntity = mlrsOwner;
ent.OwnerID = mlrsOwner.userID;
}
}
var tracker = ent.gameObject.AddComponent<ExplosiveTracker>();
if (tracker != null) {
tracker.Init(trackerCategory);
}
if (_debug) {
var pl = ent.creatorEntity as BasePlayer;
PrintDebug($"Added explosive tracker to {ent.ShortPrefabName} spawned by {pl?.displayName ?? "Unknown"}[{pl?.userID ?? 0}]");
}
}
private void OnEntityDeath(DecayEntity entity, HitInfo info) {
if (entity == null || info == null) return;
var initiator = info.Initiator;
if (initiator is FireBall) // Log fireballs spawned from inc ammo, fire arrows, etc
{
var fireballTrackerCategory = "entity_death_fire";
var fireballWeaponCfg = AddOrFindWeaponConfig(fireballTrackerCategory, initiator.ShortPrefabName);
if (!fireballWeaponCfg.enabled || IsDecayEntityIgnored(entity)) return;
var creator = initiator.creatorEntity as BasePlayer;
RaidEvent raidEventFireball = new RaidEvent {
attackerName = creator?.displayName ?? fireballWeaponCfg.name, attackerSteamID = creator?.userID ?? 0, attackerTeamID = creator?.Team?.teamID ?? 0, victimSteamID = entity.OwnerID, weapon = $"{initiator.ShortPrefabName}[{fireballTrackerCategory}]", hitEntity = $"EVENT.BURNT {GetDecayEntityShortname(entity)}", startPos = entity.transform.position + new Vector3(0, .2f, 0), endPos = entity.transform.position, timestamp = DateTime.Now
}
;
_raidEventLog.Add(raidEventFireball);
raidEventFireball.Notify(entity);
PrintDebug($"OnEntityDeath ({initiator.ShortPrefabName}) - WeaponPrefab: {info?.WeaponPrefab?.ShortPrefabName ?? "NULL"}, ProjectilePrefab: {info?.ProjectilePrefab?.name ?? "NULL"}");
PrintDebug($"{initiator.ShortPrefabName} ({initiator.creatorEntity}) BURNT {GetDecayEntityShortname(entity)}[{entity?.net?.ID.Value ?? 0}]");
return;
}
var attacker = info.InitiatorPlayer;
if (attacker == null || IsDecayEntityOrAttackerIgnored(entity, attacker)) return;
if (info?.WeaponPrefab == null && info?.damageTypes?.GetMajorityDamageType() == Rust.DamageType.Heat) // Log fires spawned from flame throwers or other weapons
{
var fireTrackerCategory = "entity_death_fire";
var fireWeapon = "fire_damage";
var fireWeaponCfg = AddOrFindWeaponConfig(fireTrackerCategory, fireWeapon);
if (!fireWeaponCfg.enabled) return;
RaidEvent raidEventFire = new RaidEvent {
attackerName = attacker?.displayName ?? "Unknown", attackerSteamID = attacker?.userID ?? 0, attackerTeamID = attacker?.Team?.teamID ?? 0, victimSteamID = entity.OwnerID, weapon = $"{fireWeapon}[{fireTrackerCategory}]", hitEntity = $"EVENT.BURNT {GetDecayEntityShortname(entity)}", startPos = entity.transform.position + new Vector3(0, .2f, 0), endPos = entity.transform.position, timestamp = DateTime.Now
}
;
_raidEventLog.Add(raidEventFire);
raidEventFire.Notify(entity);
PrintDebug($"OnEntityDeath ({fireWeapon}) - WeaponPrefab: {info?.WeaponPrefab?.ShortPrefabName ?? "NULL"}, ProjectilePrefab: {info?.ProjectilePrefab?.name ?? "NULL"}");
PrintDebug($"{fireWeapon} BURNT {GetDecayEntityShortname(entity)}[{entity?.net?.ID.Value ?? 0}]");
return;
}
var weaponPrefabShortname = info?.WeaponPrefab?.ShortPrefabName;
var projectilePrefabShortname = info?.ProjectilePrefab?.name;
var projectileItemShortname = projectilePrefabShortname != null ? GetItemFromPrefabShortname(projectilePrefabShortname) : null;
var heldEntity = attacker.GetHeldEntity();
if (heldEntity is AttackEntity) {
if (info.WeaponPrefab == null) {
weaponPrefabShortname = heldEntity.ShortPrefabName;
PrintDebug($"OnEntityDeath - WeaponPrefab is NULL! Using HeldEntity: {heldEntity.ShortPrefabName ?? "NULL"}");
}
var projectile = heldEntity?.GetComponent<BaseProjectile>();
var heldProjectileItemShortname = projectile?.primaryMagazine?.ammoType?.shortname ?? null;
if ((info.WeaponPrefab == null && info.ProjectilePrefab == null) || (projectileItemShortname != null && heldProjectileItemShortname != null && projectileItemShortname != heldProjectileItemShortname)) // certain projectiles from HitInfo do not match the projectile in the players gun Ex: ammo.pistol.hv, rifle.ammmo.hv, ammo.shotgun
{
if (_debug) {
if (projectileItemShortname != heldProjectileItemShortname) PrintDebug($"OnEntityDeath - ProjectileItemShortname ({projectileItemShortname ?? "NULL"}) != HeldProjectileItemShortname ({heldProjectileItemShortname ?? "NULL"})! Using HeldProjectileItemShortname: {heldProjectileItemShortname ?? "NULL"}");
else PrintDebug($"OnEntityDeath - WeaponPrefab + ProjectilePrefab are NULL! Using HeldEntityProjectile: {projectileItemShortname ?? "NULL"}");
}
projectileItemShortname = heldProjectileItemShortname;
}
}
var weaponTrackerCategory = "entity_death_weapon";
var weaponItemShortname = weaponPrefabShortname != null ? GetItemFromPrefabShortname(weaponPrefabShortname) : null;
var weaponEnabled = weaponItemShortname != null ? AddOrFindWeaponConfig(weaponTrackerCategory, weaponItemShortname).enabled : false;
var projectileTrackerCategory = "entity_death_ammo";
var projectileEnabled = projectileItemShortname != null && weaponItemShortname != projectileItemShortname ? AddOrFindWeaponConfig(projectileTrackerCategory, projectileItemShortname).enabled : false;
PrintDebug($"OnEntityDeath - WeaponPrefab: {info?.WeaponPrefab?.ShortPrefabName ?? "NULL"} ({weaponItemShortname ?? "NULL"}), ProjectilePrefab: {info?.ProjectilePrefab?.name ?? "NULL"} ({projectileItemShortname ?? "NULL"})");
if (!weaponEnabled && !projectileEnabled) return;
string weapon;
if (weaponEnabled && projectileItemShortname != null && weaponItemShortname != projectileItemShortname) weapon = $"{weaponItemShortname}[{weaponTrackerCategory}];{projectileItemShortname}[{projectileTrackerCategory}]";
else if (projectileEnabled && weaponItemShortname != null && weaponItemShortname != projectileItemShortname) weapon = $"{projectileItemShortname}[{projectileTrackerCategory}];{weaponItemShortname}[{weaponTrackerCategory}]";
else if (projectileEnabled) weapon = $"{projectileItemShortname}[{projectileTrackerCategory}]";
else weapon = $"{weaponItemShortname}[{weaponTrackerCategory}]";
var startPos = attacker.transform.position;
startPos.y += attacker.GetHeight() -.5f;
var endPos = info.HitPositionWorld != Vector3.zero && info.HitPositionWorld != entity.transform.position ? info.HitPositionWorld : entity.WorldSpaceBounds().ToBounds().center;
RaidEvent raidEvent = new RaidEvent {
attackerName = attacker?.displayName ?? "Unknown", attackerSteamID = attacker?.userID ?? 0, attackerTeamID = attacker?.Team?.teamID ?? 0, victimSteamID = entity.OwnerID, weapon = weapon, hitEntity = $"EVENT.DESTROYED {GetDecayEntityShortname(entity)}", startPos = startPos, endPos = endPos, timestamp = DateTime.Now
}
;
_raidEventLog.Add(raidEvent);
raidEvent.Notify(entity);
PrintDebug($"{weapon} DESTROYED {GetDecayEntityShortname(entity)}[{entity?.net?.ID.Value ?? 0}]");
}
private void OnServerSave() {
SaveRaidEventLog();
}
private void OnNewSave(string filename) {
_wipeData = true;
}
#endregion
#region Commands
[Command("x")]
private void ViewExplosionsCommand(IPlayer player, string command, string[] args) {
BasePlayer pl = player.Object as BasePlayer;
if (pl == null || !pl.IsAdmin) return;
if (args.Length > 0 && (args[0].ToLower() == "v" || args[0].ToLower() == "extra")) {
_verboseMode[pl.userID] = _verboseMode.ContainsKey(pl.userID) ? !_verboseMode[pl.userID] : true;
SendChatMsg(pl, lang.GetMessage(_verboseMode[pl.userID] ? "ViewEventsCommand.ExtraModeEnabled" : "ViewEventsCommand.ExtraModeDisabled", this, pl.UserIDString));
return;
}
float radius = _config.searchRadius > 0f ? _config.searchRadius : 50f;
string filterType = args.Length > 0 ? args[0].ToLower() : "";
string filter = "";
bool verbose = _verboseMode.ContainsKey(pl.userID) && _verboseMode[pl.userID];
Vector3 pos = pl.transform.position;
pos.y += pl.GetHeight() -.5f;
float tempRadius;
IEnumerable<IGrouping<RaidFilter, RaidEvent>> groupedRaidsNearMe;
switch (filterType) {
case "l" : case "last" : string[] lastArgs;
if (_lastViewCommand.TryGetValue(pl.userID, out lastArgs)) ViewExplosionsCommand(player, command, lastArgs);
return;
case "help" : _sb.Clear();
_sb.AppendLine(lang.GetMessage("ViewEventsCommand.HelpHeader", this, pl.UserIDString));
_sb.AppendLine(lang.GetMessage("ViewEventsCommand.HelpDefault", this, pl.UserIDString));
_sb.AppendLine(lang.GetMessage("ViewEventsCommand.HelpExtraMode", this, pl.UserIDString));
_sb.AppendLine(lang.GetMessage("ViewEventsCommand.HelpWipe", this, pl.UserIDString));
_sb.AppendLine(lang.GetMessage("ViewEventsCommand.HelpLast", this, pl.UserIDString));
_sb.AppendLine(lang.GetMessage("ViewEventsCommand.HelpFilter", this, pl.UserIDString));
_sb.Append("<indent=6>");
_sb.AppendLine(lang.GetMessage("ViewEventsCommand.HelpFilterTime", this, pl.UserIDString));
_sb.AppendLine(lang.GetMessage("ViewEventsCommand.HelpFilterWeapon", this, pl.UserIDString));
_sb.AppendLine(lang.GetMessage("ViewEventsCommand.HelpFilterEntity", this, pl.UserIDString));
_sb.AppendLine(lang.GetMessage("ViewEventsCommand.HelpFilterTeam", this, pl.UserIDString));
_sb.AppendLine(lang.GetMessage("ViewEventsCommand.HelpFilterPlayer", this, pl.UserIDString));
_sb.Append("<indent=0>");
_sb.AppendLine(lang.GetMessage("ViewEventsCommand.HelpPrintRaidEvent", this, pl.UserIDString));
SendChatMsg(pl, _sb.ToString(), "");
return;
case "wipe" : if (!permission.UserHasPermission(pl.UserIDString, PERM_WIPE)) {
SendChatMsg(pl, lang.GetMessage("ViewEventsCommand.WipePermission", this, pl.UserIDString));
return;
}
if (args.Length > 1 && float.TryParse(args[1], out tempRadius)) radius = tempRadius;
var raidEventsToWipe = FindRaidEventsInSphere(pos, radius).ToList();
if (raidEventsToWipe.Count == 0) {
SendChatMsg(pl, string.Format(lang.GetMessage("ViewEventsCommand.NotFoundRadius", this, pl.UserIDString), radius));
return;
}
foreach(var raidEvent in raidEventsToWipe) {
int idx = raidEvent.GetIndex();
if (idx > -1) _raidEventLog.RemoveAt(idx);
}
var playerPos = pl.transform.position;
var gridPos = PhoneController.PositionToGridCoord(playerPos);
var filename = $"{Name}\\WipedRaidEvents\\{string.Format(" {
0 : yyyy-MM-dd
}
", DateTime.Now)}\\{pl.userID}\\{gridPos}_{string.Format(" {
0 : h-mm-tt
}
", DateTime.Now)}";
LogToFile("wiped_raid_events", $"{pl.displayName}[{pl.userID}] wiped {raidEventsToWipe.Count} raid events in {gridPos} ({FormatPosition(playerPos)})", this);
Interface.Oxide.DataFileSystem.WriteObject(filename, raidEventsToWipe);
SaveRaidEventLog();
SendChatMsg(pl, string.Format(lang.GetMessage("ViewEventsCommand.WipedRaidEventsRadius", this, pl.UserIDString), raidEventsToWipe.Count, radius, gridPos));
return;
case "time" : filter = args.Length > 1 ? args[1].ToLower() : "";
if (args.Length > 2 && float.TryParse(args[2], out tempRadius)) radius = tempRadius;
double hours;
if (!double.TryParse(filter, out hours)) hours = 24;
groupedRaidsNearMe = FindRaidEventsInSphere(pos, radius).Where(x => DateTime.Now.Subtract(x.timestamp).TotalHours <= hours).GroupBy(x => new RaidFilter {
filter = $"{x.attackerName}[{x.attackerSteamID}]", filterType = filterType
}
);
break;
case "weapon" : filter = args.Length > 1 ? args[1].ToLower() : "";
if (args.Length > 2 && float.TryParse(args[2], out tempRadius)) radius = tempRadius;
groupedRaidsNearMe = FindRaidEventsInSphere(pos, radius).Where(x => x.weapon.Contains(filter) || FindWeaponConfig(x.GetTrackerCategory(), x.GetPrimaryWeaponShortname()).name.ToLower().Contains(filter)).GroupBy(x => new RaidFilter {
filter = FindWeaponConfig(x.GetTrackerCategory(), x.GetPrimaryWeaponShortname()).name, filterType = filterType
}
);
break;
case "entity" : filter = args.Length > 1 ? args[1].ToLower() : "";
if (args.Length > 2 && float.TryParse(args[2], out tempRadius)) radius = tempRadius;
groupedRaidsNearMe = FindRaidEventsInSphere(pos, radius).Where(x => x.hitEntity.Contains(filter)).GroupBy(x => new RaidFilter {
filter = x.GetHitEntityShortname(), filterType = filterType
}
);
break;
case "team" : filter = args.Length > 1 ? args[1].ToLower() : "";
if (args.Length > 2 && float.TryParse(args[2], out tempRadius)) radius = tempRadius;
IEnumerable<RaidEvent> tempRaidEvents = FindRaidEventsInSphere(pos, radius);
if (!string.IsNullOrEmpty(filter)) tempRaidEvents = tempRaidEvents.Where(x => x.attackerTeamID.ToString() == filter);
groupedRaidsNearMe = tempRaidEvents.GroupBy(x => {
var team = RelationshipManager.ServerInstance.FindTeam(x.attackerTeamID);
return new RaidFilter {
filter = team != null ? $"{team.GetLeader()?.displayName ?? "UNKNOWN LEADER"}'s Team (ID: {x.attackerTeamID})" : x.attackerTeamID.ToString(), filterType = filterType
}
;
}
);
break;
case "player" : filter = args.Length > 1 ? args[1].ToLower() : "";
if (args.Length > 2 && float.TryParse(args[2], out tempRadius)) radius = tempRadius;
groupedRaidsNearMe = FindRaidEventsInSphere(pos, radius).Where(x => x.attackerSteamID.ToString() == filter || x.attackerName.ToLower().Contains(filter)).GroupBy(x => new RaidFilter {
filter = $"{x.attackerName}[{x.attackerSteamID}]", filterType = filterType
}
);
break;
default : if (args.Length > 0 && float.TryParse(args[0], out tempRadius)) radius = tempRadius;
groupedRaidsNearMe = FindRaidEventsInSphere(pos, radius).GroupBy(x => new RaidFilter {
filter = $"{x.attackerName}[{x.attackerSteamID}]", filterType = "default"
}
);
break;
}
DrawRaidEvents(pl, groupedRaidsNearMe, filterType, filter, radius);
_lastViewCommand[pl.userID] = args;
}
[Command("px")]
private void PlayerViewExplosionsCommand(IPlayer player, string command, string[] args) {
BasePlayer pl = player.Object as BasePlayer;
if (pl == null) return;
bool isAdmin = player.IsAdmin;
if (!isAdmin && !permission.UserHasPermission(pl.UserIDString, PERM_PX)) {
SendChatMsg(pl, string.Format(lang.GetMessage("ViewEventsCommand.NoPermission", this, pl.UserIDString), $"/{command}"));
return;
}
try {
if (!isAdmin) {
pl.SetPlayerFlag(BasePlayer.PlayerFlags.IsAdmin, true);
pl.SendNetworkUpdateImmediate();
}
float radius = _config.searchRadius > 0f ? _config.searchRadius : 50f;
float tempRadius;
if (args.Length > 0 && float.TryParse(args[0], out tempRadius)) radius = Mathf.Clamp(tempRadius, 0f, 100f);
Vector3 pos = pl.transform.position;
pos.y += pl.GetHeight() -.5f;
var groupedRaidsNearMe = FindRaidEventsInSphere(pos, radius).Where(x => x.victimSteamID == pl.userID || (pl.Team != null && pl.Team.members.Contains(x.victimSteamID)) || FindWeaponConfig(x.GetTrackerCategory(), x.GetPrimaryWeaponShortname()).alwaysLog).Where(x => DateTime.Now.Subtract(x.timestamp).Minutes > _config.playerViewExplosionsCommand.ignoreRaidEventsLessThanMinutes).GroupBy(x => new RaidFilter {
filter = $"{x.attackerSteamID.GetHashCode()}", filterType = "victim"
}
);
DrawRaidEvents(pl, groupedRaidsNearMe, "victim", pl.UserIDString, radius, _config.playerViewExplosionsCommand.drawAttackerName);
}
catch (Exception ex) {
LogToSingleFile("px_error_log", $"error drawing player explosions for {pl.userID}[{pl?.displayName ?? "Unknown"}]\n\nException: {ex.Message}");
}
if (!isAdmin) {
pl.SetPlayerFlag(BasePlayer.PlayerFlags.IsAdmin, false);
pl.SendNetworkUpdateImmediate();
}
}
[Command("re")]
private void RaidEventDetailsCommand(IPlayer player, string command, string[] args) {
BasePlayer pl = player.Object as BasePlayer;
if (pl == null || !pl.IsAdmin) return;
int index = -1;
int i;
if (args.Length > 0 && int.TryParse(args[0], out i)) index = i;
if (index < _raidEventLog.Count && _raidEventLog[index] != null) {
RaidEvent raidEvent = _raidEventLog[index];
SendChatMsg(pl, $"\n\nRaid Event {index}:\n{JsonConvert.SerializeObject(raidEvent, Formatting.Indented)}");
}
else SendChatMsg(pl, $"Raid event {index} not found!");
}
[Command("rt.debug")]
private void DebugCommand(IPlayer player, string command, string[] args) {
if (!player.IsServer) return;
_debug = !_debug;
Puts($"debug: {_debug}");
}
[Command("rt.weapon_colors")]
private void WeaponColorsCommand(IPlayer player, string command, string[] args) {
BasePlayer pl = player.Object as BasePlayer;
if (pl == null || !pl.IsAdmin) return;
_sb.Clear();
var category = args.Length > 0 && _config.trackers.ContainsKey(args[0].ToLower()) ? args[0].ToLower() : _config.trackers.Keys.First();
foreach (var weaponCfg in _config.trackers[category]) {
var val = weaponCfg.Value;
_sb.AppendLine($"<color={val.hexColor}>• {val.name} ({category})</color> - Enabled: {val.enabled}");
}
SendChatMsg(pl, $"\n\n{_sb}");
}
#endregion
#region Helpers
private void DrawRaidEvents(BasePlayer pl, IEnumerable<IGrouping<RaidFilter, RaidEvent>> groupedRaidsNearMe, string filterType, string filter, float radius, bool drawAttackerName = true) {
bool verbose = _verboseMode.ContainsKey(pl.userID) && _verboseMode[pl.userID];
int raidCount = groupedRaidsNearMe.Sum(x => x.Count());
_sb.Clear();
_sb.AppendLine(string.Format(lang.GetMessage("ViewEventsCommand.Header", this, pl.UserIDString), raidCount, radius));
if (!string.IsNullOrEmpty(filterType)) _sb.AppendLine(string.Format(lang.GetMessage("ViewEventsCommand.Filter", this, pl.UserIDString), filterType, filter));
var groupCloseNames = new Dictionary<ulong, List<Vector3>>();
foreach (var grouping in groupedRaidsNearMe) {
var groupingColor = GetRandomColor();
var groupingColorHex = GetHexColor(groupingColor);
foreach (RaidEvent raidEvent in grouping) {
if (raidEvent.attackerTeamID > 0 && !_teamColors.ContainsKey(raidEvent.attackerTeamID)) {
_teamColors[raidEvent.attackerTeamID] = _uniqueColors[_currentTeamColorIdx];
_currentTeamColorIdx = _currentTeamColorIdx < _uniqueColors.Count() ? _currentTeamColorIdx : -1;
_currentTeamColorIdx++;
}
var teamColor = raidEvent.attackerTeamID > 0 ? _teamColors[raidEvent.attackerTeamID] : GetRandomColor();
var teamColorHex = GetHexColor(teamColor);
var weaponCfg = FindWeaponConfig(raidEvent.GetTrackerCategory(), raidEvent.GetPrimaryWeaponShortname());
var weaponColor = weaponCfg.GetWeaponColor();
if (filterType == "weapon") {
groupingColor = weaponColor;
groupingColorHex = GetHexColor(groupingColor);
}
else if (filterType == "team") {
groupingColor = teamColor;
groupingColorHex = GetHexColor(groupingColor);
}
string startText, endText;
var startPos = raidEvent.startPos;
var endPos = raidEvent.endPos;
if (weaponCfg.shortArrow) startPos = raidEvent.endPos + new Vector3(0, .2f, 0);
string attackerTeam = raidEvent.attackerTeamID > 0 ? string.Format(lang.GetMessage("ViewEventsCommand.Team", this, pl.UserIDString), teamColorHex, raidEvent.attackerTeamID) : "";
if (verbose) {
startText = string.Format(lang.GetMessage("ViewEventsCommand.StartTextExtra", this, pl.UserIDString), drawAttackerName ? raidEvent.attackerName : "X", raidEvent.attackerSteamID, attackerTeam);
endText = string.Format(lang.GetMessage("ViewEventsCommand.EndTextExtra", this, pl.UserIDString), groupingColorHex, raidEvent.GetIndex(), raidEvent.GetWeaponName(), raidEvent.hitEntity.Replace(raidEvent.GetEventType(), raidEvent.GetPrettyEventType()));
}
else {
startText = string.Format(lang.GetMessage("ViewEventsCommand.StartText", this, pl.UserIDString), drawAttackerName ? raidEvent.attackerName : "X", attackerTeam);
endText = string.Format(lang.GetMessage("ViewEventsCommand.EndText", this, pl.UserIDString), groupingColorHex, raidEvent.GetWeaponName());
}
if (raidEvent.hitEntity.Contains("EVENT.ATTACHED")) Box(pl, endPos, .05f, groupingColor, _config.drawDuration);
else if (raidEvent.hitEntity.Contains("EVENT.HIT")) Sphere(pl, endPos, .05f, groupingColor, _config.drawDuration);
if (!groupCloseNames.ContainsKey(raidEvent.attackerSteamID)) groupCloseNames[raidEvent.attackerSteamID] = new List<Vector3>();
if (!groupCloseNames[raidEvent.attackerSteamID].Any(x => Vector3.Distance(x, startPos) <.1f)) {
groupCloseNames[raidEvent.attackerSteamID].Add(startPos);
Text(pl, startPos, startText, groupingColor, _config.drawDuration);
}
Arrow(pl, startPos, endPos, .05f, groupingColor, _config.drawDuration);
Text(pl, endPos + new Vector3(0, .05f, 0), endText, weaponColor, _config.drawDuration);
}
_sb.AppendLine(string.Format(lang.GetMessage("ViewEventsCommand.GroupingCount", this, pl.UserIDString), groupingColorHex, grouping.Count(), grouping.Key.filterType, grouping.Key.filter));
var weaponCounts = grouping.GroupBy(x => new {
weapon = x.GetPrimaryWeaponShortname(), trackerCategory = x.GetTrackerCategory()
}
).Select(x => new {
data = x.Key, count = x.Count()
}
);
_sb.Append("<indent=6>");
foreach (var x in weaponCounts) {
var weaponCfg = FindWeaponConfig(x.data.trackerCategory, x.data.weapon);
_sb.Append(string.Format(lang.GetMessage("ViewEventsCommand.WeaponCount", this, pl.UserIDString), weaponCfg.hexColor, x.count, weaponCfg.name, x.data.trackerCategory));
}
_sb.Append("<indent=0>");
_sb.AppendLine($"\n");
}
if (raidCount < 1) _sb.AppendLine(lang.GetMessage("ViewEventsCommand.NotFound", this, pl.UserIDString));
SendChatMsg(pl, _sb.ToString().TrimEnd(), "");
}
private void LogToSingleFile(string filename, string text) => LogToFile(filename, string.Format("[{0:yyyy-MM-dd HH:mm:ss}] {1}", DateTime.Now, text), this, false);
private void PrintDebug(string msg) {
if (_debug) Puts($"[DEBUG] {msg}");
}
private void SendChatMsg(BasePlayer pl, string msg, string prefix = null) {
var p = prefix != null ? prefix : lang.GetMessage("ChatPrefix", this, pl.UserIDString);
_rustPlayer.Message(pl, msg, p, _config.chatIconID, Array.Empty<object>());
if (_config.printToClientConsole) pl.ConsoleMessage($"{(!string.IsNullOrEmpty(p) ? $" {
p
}
" : "")}{msg}");
}
public void Arrow(BasePlayer player, Vector3 from, Vector3 to, float headSize, Color color, float duration) => player.SendConsoleCommand("ddraw.arrow", duration, color, from, to, headSize);
public void Sphere(BasePlayer player, Vector3 pos, float radius, Color color, float duration) => player.SendConsoleCommand("ddraw.sphere", duration, color, pos, radius);
public void Box(BasePlayer player, Vector3 pos, float size, Color color, float duration) => player.SendConsoleCommand("ddraw.box", duration, color, pos, size);
public void Text(BasePlayer player, Vector3 pos, string text, Color color, float duration) => player.SendConsoleCommand("ddraw.text", duration, color, pos, text);
private void AddPrefabToItem(string prefab, string itemShortname, string prefabSource) {
var prefabShortname = GetPrefabShortname(prefab);
if (_prefabToItem.ContainsKey(prefabShortname)) return;
_prefabToItem[prefabShortname] = itemShortname;
PrintDebug($"prefabToItem - {prefabSource}: {prefabShortname} -> {itemShortname}");
}
private string GetItemFromPrefabShortname(string prefabShortname) => _prefabToItem.ContainsKey(prefabShortname) ? _prefabToItem[prefabShortname] : prefabShortname;
private string GetPrefabShortname(string prefab) => prefab.Substring(prefab.LastIndexOf('/') + 1).Replace(".prefab", "");
private string GetPrettyItemName(string itemShortname) {
string buildingBlockName;
if (_buildingBlockPrettyNames.TryGetValue(itemShortname, out buildingBlockName)) return buildingBlockName;
return ItemManager.FindItemDefinition(itemShortname)?.displayName?.english ?? itemShortname;
}
private IEnumerable<RaidEvent> FindRaidEventsInSphere(Vector3 pos, float r) => _raidEventLog.Where(x => Vector3.Distance(pos, x.startPos) < r || Vector3.Distance(pos, x.endPos) < r);
private Color GetRandomColor() => UnityEngine.Random.ColorHSV(0f, 1f, .4f, .8f, .5f, 1f);
private string GetHexColor(Color color) => $"#{ColorUtility.ToHtmlStringRGB(color)}";
private PluginConfig.WeaponConfig AddOrFindWeaponConfig(string category, string shortname) {
if (!_config.trackers.ContainsKey(category)) _config.trackers[category] = new SortedDictionary<string, PluginConfig.WeaponConfig>();
if (!_config.trackers[category].ContainsKey(shortname)) {
var weaponCfg = new PluginConfig.WeaponConfig {
enabled = _config.enableNewTrackers, name = GetPrettyItemName(GetItemFromPrefabShortname(shortname)), hexColor = GetHexColor(GetRandomColor())
}
;
_config.trackers[category][shortname] = weaponCfg;
LogToSingleFile("weapon_config_log", $"added {weaponCfg.name} ({category} / {shortname}), enabled: {weaponCfg.enabled}");
SaveConfig();
}
return FindWeaponConfig(category, shortname);
}
private PluginConfig.WeaponConfig FindWeaponConfig(string category, string shortname) {
PluginConfig.WeaponConfig weaponCfg;
if (_config.trackers.ContainsKey(category) && _config.trackers[category].TryGetValue(shortname, out weaponCfg)) {
if (_dev) {
return new PluginConfig.WeaponConfig {
enabled = true, name = weaponCfg.name, hexColor = weaponCfg.hexColor, alwaysLog = weaponCfg.alwaysLog, shortArrow = weaponCfg.shortArrow, discordIcon = weaponCfg.discordIcon, notifyConsole = true, notifyAdmin = true, notifyDiscord = true, logToFile = true
}
;
}
PluginConfig.WeaponConfig globalWeaponCfg = null;
if (_config.trackers.ContainsKey("_global") && _config.trackers["_global"].ContainsKey("*")) globalWeaponCfg = _config.trackers["_global"]["*"];
if ((globalWeaponCfg == null || !globalWeaponCfg.enabled) && _config.trackers[category].ContainsKey("*")) globalWeaponCfg = _config.trackers[category]["*"];
if (!weaponCfg.enabled && globalWeaponCfg.enabled) {
return new PluginConfig.WeaponConfig {
enabled = globalWeaponCfg.enabled, name = weaponCfg.name, hexColor = weaponCfg.hexColor, alwaysLog = weaponCfg.alwaysLog, shortArrow = weaponCfg.shortArrow, discordIcon = weaponCfg.discordIcon, notifyConsole = globalWeaponCfg.notifyConsole, notifyAdmin = globalWeaponCfg.notifyAdmin, notifyDiscord = globalWeaponCfg.notifyDiscord, logToFile = globalWeaponCfg.logToFile
}
;
}
return weaponCfg;
}
throw new Exception($"THIS SHOULD NEVER HAPPEN! Unable to find weapon config [{category} / {shortname}]");
}
private string GetDecayEntityShortname(DecayEntity entity) {
var buildingBlock = entity as BuildingBlock;
return $"{entity.ShortPrefabName}{(buildingBlock != null ? $". {
buildingBlock.grade.ToString().ToLower()
}
" : "")}";
}
private bool IsDecayEntityIgnored(DecayEntity entity) {
if (entity is LootContainer || entity.OwnerID == 0) return true;
if (!_dev && _decayEntityIgnoreList.ContainsKey(entity.PrefabName) && _decayEntityIgnoreList[entity.PrefabName].ignore) return true;
var buildingBlock = entity as BuildingBlock;
bool ignoreGrade;
if (!_dev && buildingBlock != null && _config.ignoreBuildingGrades.TryGetValue(buildingBlock.grade, out ignoreGrade) && ignoreGrade) return true;
if (Convert.ToBoolean(RaidableBases?.Call("EventTerritory", entity.transform.position))) return true;
if (Convert.ToBoolean(AbandonedBases?.Call("EventTerritory", entity.transform.position))) return true;
return false;
}
private bool IsDecayEntityOrAttackerIgnored(DecayEntity entity, BasePlayer attacker) {
if (IsDecayEntityIgnored(entity)) return true;
if (attacker != null && attacker.IsAdmin) return false;
if (!_dev && _config.ignoreSameOwner && attacker != null && attacker.userID == entity.OwnerID) return true;
if (!_dev && _config.ignoreTeamMember && attacker != null && attacker.Team != null && attacker.Team.members.Contains(entity.OwnerID)) return true;
if (!_dev && _config.ignoreClanMemberOrAlly && attacker != null && Convert.ToBoolean(Clans?.Call("IsMemberOrAlly", attacker.UserIDString, entity.OwnerID.ToString()))) return true;
return false;
// ... [Code preview truncated due to length. Full source in attached file] ...