C#:
// Все права принадлежат дискорд сообществу https://discord.gg/VgNHPpNrz6
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.Serialization;
using System.Text;
using Facepunch;
using Facepunch.Math;
using Newtonsoft.Json;
using Oxide.Core;
using Oxide.Core.Plugins;
using Oxide.Game.Rust.Cui;
using Rust;
using Rust.Ai.Gen2;
using UnityEngine;
using ProtoBuf;
/**********************************************************************
*
* 2.4.0 - Minor adjustments
* Fixed most chatmessages in corrected language
* Removed a hardcoded debug
* Added Killfeed text allignment (left/right) to cfg and methods
* Fix for dupe tags
* Added support for wolf2 (make sure to add it to the cfg file manualy)
* Added API : Interface.Call("OnKillFeedMessageReceived" , text);
* 2.4.1 - Added permission simplekillfeed.exclude
* 2.4.2 - Added more checks for The new animal AI
*
***********************************************************************/
namespace Oxide.Plugins {
[Info("Simple Kill Feed", "Krungh Crow", "2.4.2")]
[Description("A kill feed, that displays various kill events in the top right corner.")]
public class SimpleKillFeed : RustPlugin {
#region Fields
[PluginReference]
private Plugin BetterChat , Clans;
private bool _isClansReborn;
private bool _isClansIo;
private bool _isClansMevent;
private readonly Dictionary<uint, string> _itemNameMapping = new Dictionary<uint, string>();
private GameObject _holder;
private KillQueue _killQueue;
private static SKFConfig configdata;
private static SimpleKillFeedData _data;
const string Exclude_Perm = "simplekillfeed.exclude";
#endregion
#region Config
private class SKFConfig {
[JsonProperty("Show Traps and Entitys in Kill Feed")]
public bool EnableEntityFeed;
[JsonProperty("Show Animals kills (default true)")]
public bool EnableAnimalFeed;
[JsonProperty("Show Npcs kills (Default true)")]
public bool EnableNpcFeed;
[JsonProperty("Show suicides (Default: true)")]
public bool EnableSuicides;
[JsonProperty("Show Deaths by Animals (Default: true)")]
public bool EnableAnimal;
[JsonProperty("Show Deaths by Cold (Default: true)")]
public bool EnableCold;
[JsonProperty("Show deaths by Drowning (Default: true)")]
public bool EnableDrowning;
[JsonProperty("Show Deaths by Fall (Default: true)")]
public bool EnableFall;
[JsonProperty("Show Deaths by Hunger (Default: true)")]
public bool EnableHunger;
[JsonProperty("Show Deaths by Electricution (Default: true)")]
public bool EnableElectricution;
[JsonProperty("Show Deaths by Radiation (Default: true)")]
public bool EnableRadiationKills;
[JsonProperty("Chat Icon Id (Steam profile ID)")]
public ulong IconId;
[JsonProperty("Max messages in feed (Default: 5)")]
public int MaxFeedMessages;
[JsonProperty("Max player name length in feed (Default: 18)")]
public int MaxPlayerNameLength;
[JsonProperty("Feed message TTL in seconds (Default: 7)")]
public int FeedMessageTtlSec;
[JsonProperty("Allow kill messages in chat along with kill feed")]
public bool EnableChatFeed;
[JsonProperty("Log PvP Kill events")]
public bool EnableLogging;
[JsonProperty("Height ident (space between messages).Default: 0.0185")]
public float HeightIdent;
[JsonProperty("Feed Position - Anchor Max.(Default: 0.995 0.986")]
public string AnchorMax;
[JsonProperty("Feed Position - Anchor Min.(Default: 0.723 0.964")]
public string AnchorMin;
[JsonProperty("Font size of kill feed (Default: 12)")]
public int FontSize;
[JsonProperty("Default textanchor (left or right)")]
public string TextAnchor;
[JsonProperty("Outline Text Size (Default: 0.5 0.5)")]
public string OutlineSize;
[JsonProperty("Default color for distance (if too far from any from the list).Default: #FF8000")]
public string DefaultDistanceColor;
[JsonProperty("Distance Colors List (Certain color will apply if distance is <= than specified)")]
public DistanceColor[] DistanceColors;
[JsonProperty("Custom Entity Names, you can remove or add more!")]
public Dictionary<string, string> Ents = new Dictionary<string, string>();
[JsonProperty("Custom Animal Names, you can remove or add more!")]
public Dictionary<string, string> Animal = new Dictionary<string, string>();
[JsonProperty("Custom Weapon Names, you can add more!")]
public Dictionary<string, string> Weapons = new Dictionary<string, string>();
[JsonProperty("Custom Npc Names, you can add more!")]
public Dictionary<string, string> Npcs = new Dictionary<string, string>();
[OnDeserialized]
internal void OnDeserialized(StreamingContext ctx) => Array.Sort(DistanceColors, (o1, o2) => o1.DistanceThreshold.CompareTo(o2.DistanceThreshold));
public class DistanceColor {
public int DistanceThreshold;
public string Color;
public bool TestDistance(int distance) => distance <= DistanceThreshold;
}
}
protected override void LoadConfig() {
base.LoadConfig();
configdata = Config.ReadObject<SKFConfig>();
if (string.IsNullOrEmpty(configdata.TextAnchor)) {
configdata.TextAnchor = "right";
SaveConfig();
Puts("Adding TextAnchor to the cfg");
}
}
protected override void LoadDefaultConfig() {
configdata = new SKFConfig {
EnableEntityFeed = true, EnableAnimalFeed = true, EnableNpcFeed = true, EnableFall = true , EnableHunger = true , EnableElectricution = true, EnableSuicides = true, EnableAnimal = true , EnableCold = true , EnableDrowning = true, EnableRadiationKills = true, IconId = 76561197960839785UL, MaxFeedMessages = 5, MaxPlayerNameLength = 18, FeedMessageTtlSec = 7, EnableChatFeed = true, EnableLogging = false, HeightIdent = 0.0185f, AnchorMax = "0.995 0.986", AnchorMin = "0.723 0.964", FontSize = 12, TextAnchor = "right" , OutlineSize = "0.5 0.5", DefaultDistanceColor = "#FF8000", DistanceColors = new[] {
new SKFConfig.DistanceColor {
Color = "#FFFFFF", DistanceThreshold = 50
}
, new SKFConfig.DistanceColor {
Color = "#91D6FF", DistanceThreshold = 100
}
, new SKFConfig.DistanceColor {
Color = "#FFFF00", DistanceThreshold = 150
}
}
, Ents = new Dictionary<string, string>() {
{
"autoturret_deployed", "Auto Turret"
}
, {
"flameturret.deployed", "Flame Turret"
}
, {
"guntrap.deployed", "Gun Trap"
}
, {
"landmine", "Landmine"
}
, {
"beartrap", "Bear Trap"
}
, {
"sam_site_turret_deployed", "Sam Site Turret"
}
, {
"patrolhelicopter", "Helicopter"
}
, {
"bradleyapc", "Bradley APC"
}
}
, Animal = new Dictionary<string, string>() {
{
"bear", "Bear"
}
, {
"polarbear", "PolarBear"
}
, {
"wolf", "Wolf"
}
, {
"wolf2", "Wild wolf"
}
, {
"stag", "Stag"
}
, {
"boar", "Boar"
}
, {
"chicken", "Chicken"
}
, {
"horse", "Horse"
}
, {
"simpleshark", "Shark"
}
}
, Weapons = new Dictionary<string, string>() {
{
"Assault Rifle", "Ak-47"
}
, {
"LR-300 Assault Rifle", "LR-300"
}
, {
"L96 Rifle", "L96"
}
, {
"Bolt Action Rifle", "Bolt"
}
, {
"Semi-Automatic Rifle", "Semi-AR"
}
, {
"Semi-Automatic Pistol", "Semi-AP"
}
, {
"Spas-12 Shotgun", "Spas-12"
}
, {
"M92 Pistol", "M92"
}
}
, Npcs = new Dictionary<string, string>() {
{
"scientist", "Scientist Npc"
}
}
}
;
}
protected override void SaveConfig() => Config.WriteObject(configdata);
#endregion
#region Data (ProtoBuf)
[ProtoContract(ImplicitFields = ImplicitFields.AllPublic)]
private class SimpleKillFeedData {
public HashSet<ulong> DisabledUsers = new HashSet<ulong>();
}
private void LoadData() {
_data = ProtoStorage.Load<SimpleKillFeedData>(nameof(SimpleKillFeed)) ?? new SimpleKillFeedData();
}
private void SaveData() {
if (_data == null) return;
ProtoStorage.Save(_data, nameof(SimpleKillFeed));
}
#endregion
#region ChatCommand
[ChatCommand("feed")]
private void ToggleFeed(BasePlayer player) {
if (!_data.DisabledUsers.Contains(player.userID)) {
_data.DisabledUsers.Add(player.userID);
Player.Message(player, _("Disabled", player), null, configdata.IconId);
}
else {
_data.DisabledUsers.Remove(player.userID);
Player.Message(player, _("Enabled", player), null, configdata.IconId);
}
}
#endregion
#region Oxide Hooks
private void OnServerInitialized() {
_isClansReborn = Clans != null && Clans.Author.Contains("k1lly0u");
_isClansIo = Clans != null && Clans.Author.Contains("playrust.io / dcode");
_isClansMevent = Clans != null && Clans.Author.Contains("mevent");
foreach (var blueprint in ItemManager.bpList.Where(bp => bp.targetItem.category == ItemCategory.Weapon || bp.targetItem.category == ItemCategory.Tool)) {
var md = blueprint.targetItem.GetComponent<ItemModEntity>();
if (!md) continue;
if (!_itemNameMapping.ContainsKey(md.entityPrefab.resourceID)) _itemNameMapping.Add(md.entityPrefab.resourceID , blueprint.targetItem.displayName.english);
}
_holder = new GameObject("SKFHolder");
UnityEngine.Object.DontDestroyOnLoad(_holder);
_killQueue = _holder.AddComponent<KillQueue>();
_killQueue.SetPlugin(this);
//Add the plugin to the class
Pool.ResizeBuffer<KillEvent>(configdata.MaxFeedMessages);
permission.RegisterPermission(Exclude_Perm , this);
}
private void Init() => LoadData();
private void Unload() {
_killQueue = null;
UnityEngine.Object.Destroy(_holder);
_holder = null;
for (var i = 0; i < configdata.MaxFeedMessages; i++) KillQueue.RemoveKillCui($"kf-{i}");
configdata = null;
Pool.IPoolCollection value;
Pool.Directory.TryRemove(typeof(KillEvent), out value);
SaveData();
_data = null;
}
#endregion
#region Oxide Hooks (Deaths)
private void OnEntityDeath(BaseEntity victim , HitInfo hitInfo) {
if (victim == null || victim is BasePlayer) return;
// Check if victim is either an animal or a specific NPC type
if (!(victim is BaseAnimalNPC || victim is BaseNPC2) || !IsAnimal(victim)) return;
BasePlayer attacker = hitInfo.InitiatorPlayer;
if (attacker == null || attacker.IsNpc || IsAnimal(attacker) || IsZombieHorde(attacker)) return;
if (!attacker.userID.IsSteamId()) return;
if (!configdata.EnableAnimalFeed) return;
// Determine victim name based on its type
string VictimName;
if (victim is BaseAnimalNPC animalVictim) {
VictimName = configdata.Animal[animalVictim.ShortPrefabName];
}
else if (victim is BaseNPC2 npcVictim) {
VictimName = npcVictim.name.Contains("Wolf2") ? "Wolf2" : configdata.Animal[npcVictim.ShortPrefabName];
}
else {
return;
// Exit if victim is neither BaseAnimalNPC nor BaseNPC2
}
string AttackerName = SanitizeName(GetClan(attacker) + attacker.displayName);
string WeaponName = GetCustomWeaponName(hitInfo);
var distance = (int)Vector3.Distance(attacker.transform.position , victim.transform.position);
_killQueue.OnDeath(attacker , null , string.Format(_("MsgFeedKillAnimalFromPlayer") , AttackerName , VictimName , WeaponName , GetDistanceColor(distance) , distance));
}
private void OnEntityDeath(SimpleShark victim, HitInfo hitInfo) {
if (victim == null || victim is BasePlayer || !IsAnimal(victim)) return;
BasePlayer attacker = hitInfo.InitiatorPlayer;
if (attacker == null || attacker.IsNpc || IsAnimal(attacker) || IsZombieHorde(attacker)) return;
if (!configdata.EnableAnimalFeed) return;
string VictimName = configdata.Animal[victim.ShortPrefabName];
string AttackerName = SanitizeName(GetClan(attacker) + attacker.displayName);
string WeaponName = GetCustomWeaponName(hitInfo);
var distance = (int)Vector3.Distance(attacker.transform.position, victim.transform.position);
_killQueue.OnDeath(attacker, null, string.Format(_("MsgFeedKillAnimalFromPlayer"), AttackerName, VictimName, WeaponName, GetDistanceColor(distance), distance));
}
private void OnPlayerDeath(BasePlayer victim , HitInfo hitInfo) {
try {
if (victim == null) return;
var wAttacker = victim.lastAttacker?.ToPlayer();
if (!victim.userID.IsSteamId() && !configdata.EnableNpcFeed) {
return;
}
// Modified section to check for BaseNPC2 *before* checking IsWounded.if ((hitInfo.Initiator is BaseAnimalNPC || hitInfo.Initiator is BaseNPC2))
{
if (!configdata.EnableAnimal) return;
// Check EnableAnimal before processing
if (hitInfo.Initiator is BaseNPC2 && !configdata.EnableNpcFeed) return;
// Check EnableNpcFeed when killed by BaseNPC2
OnKilledByAnimal(hitInfo.Initiator , victim);
return;
}
if (IsTrap(hitInfo.Initiator)) {
if (!configdata.EnableEntityFeed) return;
OnKilledByEnt(hitInfo.Initiator , victim);
return;
}
if (victim.lastAttacker is NPCAutoTurret) {
if (!configdata.EnableEntityFeed) return;
OnSentry(victim);
return;
}
if (victim.lastAttacker.ToString().Contains("cactus")) {
if (!victim.userID.IsSteamId() && !configdata.EnableNpcFeed) return;
OnCactus(victim);
return;
}
if (!wAttacker.userID.IsSteamId() && !victim.userID.IsSteamId()) {
//Puts($"Npc kill : {wAttacker} Killed {victim}");
return;
}
if ((wAttacker.userID.IsSteamId() && !victim.userID.IsSteamId()) && !configdata.EnableNpcFeed) {
return;
}
if (hitInfo == null && (wAttacker != null && victim.IsWounded())) {
OnDeathFromWounds(wAttacker , victim);
return;
}
if (victim.lastAttacker?.ToPlayer() == victim) {
if (!configdata.EnableSuicides) return;
OnSuicide(victim);
return;
return;
}
if (!victim.userID.IsSteamId() && hitInfo.Initiator is HotAirBalloon) return;
if (hitInfo.WeaponPrefab.prefabID == 3032863244) //Cannon Shell prefab id
{
if (!configdata.EnableEntityFeed) return;
OnKilledByBradley(victim);
return;
}
if (IsRadiation(hitInfo)) {
if (!configdata.EnableRadiationKills) return;
OnKilledByRadiation(victim);
return;
}
if (IsFall(hitInfo) && victim.IsWounded()) {
if (!configdata.EnableFall) return;
DeathByFall(victim);
return;
}
if (IsCold(hitInfo)) {
if (!configdata.EnableCold) return;
OnFrozen(victim);
return;
}
if (IsDrowning(hitInfo)) {
if (!configdata.EnableDrowning) return;
OnDrowning(victim);
return;
}
if (IsHunger(hitInfo) || IsThirst(hitInfo) || IsPoison(hitInfo)) {
if (!configdata.EnableHunger) return;
OnHunger(victim);
return;
}
if (IsShock(hitInfo)) {
if (!configdata.EnableElectricution) return;
OnShock(victim);
return;
return;
}
var distance = !hitInfo.IsProjectile() ? (int)Vector3.Distance(hitInfo.PointStart , hitInfo.HitPositionWorld) : (int)hitInfo.ProjectileDistance;
if (IsCar(hitInfo)) distance = 0;
var attacker = hitInfo.InitiatorPlayer;
if (attacker == null) return;
else if (IsFlame(hitInfo)) {
OnBurned(attacker , victim);
}
//if (victim.IsNpc && IsExplosion(hitInfo)) return;//BotRespawn npc's can explode throwing errors
else if (!IsDrowning(hitInfo)) {
OnKilled(attacker , victim , hitInfo , distance);
return;
}
return;
}
catch {
return;
}
}
#endregion
#region LanguageAPI
protected override void LoadDefaultMessages() {
lang.RegisterMessages(new Dictionary<string , string> {
{
"MsgAttacker", "You killed <color=#ff686b>{0}</color> from {1}m in <color=#ff686b>{2}</color>."
}
, {
"MsgVictim", "<color=#ff686b>{0}</color> killed you from {1}m with their {2} to <color=#ff686b>{3}</color>."
}
, {
"MsgFeedKill", "<color=#00ff00>{0}</color> killed <color=#ff686b>{1}</color>, <color=#ff686b>{2}</color>, <color=#ff686b>{3}</color><color={4}>({5}m)</color>"
}
, {
"MsgFeedKillNpc", "<color=#00ff00>{0}</color> killed <color=#ff686b>{1}</color>, <color={2}>({3}m)</color>"
}
, {
"MsgFeedKillAnimalFromPlayer", "<color=#00ff00>{0}</color> killed a <color=#ff686b>{1}</color>, <color=#ff686b>{2}</color>, <color={3}>({4}m)</color>"
}
, {
"MsgAtkWounded", "You wounded <color=#ff686b>{0}</color> till death."
}
, {
"MsgVictimWounded", "<color=#ff686b>{0}</color> has wounded you till death."
}
, {
"MsgFeedWounded", "<color=#00ff00>{0}</color> finished <color=#ff686b>{1}</color>"
}
, {
"MsgAtkBurned", "You burned <color=#ff686b>{0}</color> alive!"
}
, {
"MsgVictimBurned", "<color=#ff686b>{0}</color> has burned you alive!"
}
, {
"MsgFeedBurned", "<color=#00ff00>{0}</color> burned <color=#ff686b>{1}</color>!"
}
, {
"MsgFeedKillBrad", "<color=#ff686b>{0}</color> was killed by a <color=orange>Shell</color>"
}
, {
"MsgFeedKillEnt", "<color=#ff686b>{0}</color> was killed by <color=orange>{1}</color>"
}
, {
"MsgFeedKillAnimal", "<color=#ff686b>{0}</color> was killed by <color=orange>{1}</color>"
}
, {
"MsgFeedKillSuicide", "<color=#ff686b>{0}</color> committed <color=orange>Suicide</color>"
}
, {
"MsgFeedKillRadiation", "<color=#ff686b>{0}</color> died to <color=orange>Radiation</color>"
}
, {
"MsgFeedKillFall", "<color=#ff686b>{0}</color> died by <color=orange>Fall</color>"
}
, {
"MsgFeedKillCold", "<color=#ff686b>{0}</color> died by <color=orange>Cold</color>"
}
, {
"MsgFeedKillDrowned", "<color=#ff686b>{0}</color> <color=orange>Drowned</color>"
}
, {
"MsgFeedKillHunger", "<color=#ff686b>{0}</color> <color=orange>Starved</color>"
}
, {
"MsgFeedKillShock", "<color=#ff686b>{0}</color> got <color=orange>Electrocuted</color>"
}
, {
"MsgFeedKillSentry", "<color=#ff686b>{0}</color> got killed by <color=orange>Outpost</color>"
}
, {
"MsgFeedKillCactus", "<color=#ff686b>{0}</color> died to a <color=orange>Cactus</color>"
}
, {
"Enabled", "KillFeed Enabled"
}
, {
"Disabled", "KillFeed Disabled"
}
}
, this);
lang.RegisterMessages(new Dictionary<string , string> {
{
"MsgAttacker", "Du hast <color=#ff686b>{0}</color> aus {1}m in <color=#ff686b>{2}</color> gettet."
}
, {
"MsgVictim", "<color=#ff686b>{0}</color> hat dich aus {1}m mit ihrer {2} zu <color=#ff686b>{3}</color> gettet."
}
, {
"MsgFeedKill", "<color=#00ff00>{0}</color> hat <color=#ff686b>{1}</color> gettet, <color=#ff686b>{2}</color>, <color=#ff686b>{3}</color><color={4}>({5}m)</color>"
}
, {
"MsgFeedKillNpc", "<color=#00ff00>{0}</color> hat <color=#ff686b>{1}</color> gettet, <color={2}>({3}m)</color>"
}
, {
"MsgFeedKillAnimalFromPlayer", "<color=#00ff00>{0}</color> hat ein <color=#ff686b>{1}</color> gettet, <color=#ff686b>{2}</color>, <color={3}>({4}m)</color>"
}
, {
"MsgAtkWounded", "Du hast <color=#ff686b>{0}</color> bis zum Tod verwundet."
}
, {
"MsgVictimWounded", "<color=#ff686b>{0}</color> hat dich bis zum Tod verwundet."
}
, {
"MsgFeedWounded", "<color=#00ff00>{0}</color> hat <color=#ff686b>{1}</color> erledigt."
}
, {
"MsgAtkBurned", "Du hast <color=#ff686b>{0}</color> lebendig verbrannt!"
}
, {
"MsgVictimBurned", "<color=#ff686b>{0}</color> hat dich lebendig verbrannt!"
}
, {
"MsgFeedBurned", "<color=#00ff00>{0}</color> hat <color=#ff686b>{1}</color> verbrannt!"
}
, {
"MsgFeedKillBrad", "<color=#ff686b>{0}</color> wurde von einer <color=orange>Schale</color> gettet."
}
, {
"MsgFeedKillEnt", "<color=#ff686b>{0}</color> wurde von <color=orange>{1}</color> gettet."
}
, {
"MsgFeedKillAnimal", "<color=#ff686b>{0}</color> wurde von <color=orange>{1}</color> gettet."
}
, {
"MsgFeedKillSuicide", "<color=#ff686b>{0}</color> beging <color=orange>Suizid</color>."
}
, {
"MsgFeedKillRadiation", "<color=#ff686b>{0}</color> starb an <color=orange>Strahlung</color>."
}
, {
"MsgFeedKillFall", "<color=#ff686b>{0}</color> starb durch einen <color=orange>Fall</color>."
}
, {
"MsgFeedKillCold", "<color=#ff686b>{0}</color> starb an <color=orange>Klte</color>."
}
, {
"MsgFeedKillDrowned", "<color=#ff686b>{0}</color> <color=orange>Ertrunken</color>."
}
, {
"MsgFeedKillHunger", "<color=#ff686b>{0}</color> <color=orange>Hungergestorben</color>."
}
, {
"MsgFeedKillShock", "<color=#ff686b>{0}</color> wurde <color=orange>elektrisiert</color>."
}
, {
"MsgFeedKillSentry", "<color=#ff686b>{0}</color> wurde von einem <color=orange>Auenposten</color> gettet."
}
, {
"MsgFeedKillCactus", "<color=#ff686b>{0}</color> starb an einem <color=orange>Kaktus</color>."
}
, {
"Enabled", "KillFeed Aktiviert"
}
, {
"Disabled", "KillFeed Deaktiviert"
}
}
, this , "de");
}
#endregion
#region API for other plugins to use the feed
private void SendKillfeedmessage(string msg) {
_killQueue.OnDeath(null , null , string.Format(_(msg)));
}
#endregion
#region Kill Events
private void OnKilled(BasePlayer attacker, BasePlayer victim, HitInfo hitInfo, int dist) {
var HitBone = hitInfo.boneArea.ToString();
if (HitBone == "-1") HitBone = "Body";
if (IsCar(hitInfo)) dist = 0;
if (configdata.EnableChatFeed == true) {
if (!_data.DisabledUsers.Contains(attacker.userID)) Player.Message(attacker , _("MsgAttacker" , attacker) , null , configdata.IconId , GetClan(victim) + victim.displayName , dist , HitBone);
if (!_data.DisabledUsers.Contains(victim.userID)) Player.Message(victim , _("MsgVictim" , victim) , null , configdata.IconId , GetClan(attacker) + attacker.displayName , dist , GetCustomWeaponName(hitInfo) , HitBone);
}
if ((IsZombieHorde(attacker) || attacker.IsNpc) && configdata.EnableNpcFeed) {
var npc = attacker;
_killQueue.OnDeath(victim, null, string.Format(_("MsgFeedKillNpc"), CustomNpcName(npc), SanitizeName(GetClan(victim) + victim.displayName), GetDistanceColor(dist), dist));
}
if ((victim.IsNpc || IsZombieHorde(victim)) && configdata.EnableNpcFeed) {
var npc = victim;
_killQueue.OnDeath(attacker, null, string.Format(_("MsgFeedKill"), SanitizeName(GetClan(attacker) + attacker.displayName), CustomNpcName(npc), GetCustomWeaponName(hitInfo), HitBone, GetDistanceColor(dist), dist));
}
else _killQueue.OnDeath(victim, attacker, string.Format(_("MsgFeedKill"), SanitizeName(GetClan(attacker) + attacker.displayName), SanitizeName(GetClan(victim) + victim.displayName), GetCustomWeaponName(hitInfo), HitBone, GetDistanceColor(dist), dist));
if (!configdata.EnableLogging) return;
var sfkLog = new StringBuilder($"{DateTime.Now}: ({attacker.UserIDString}){attacker.displayName} killed ({victim.UserIDString}){victim.displayName} from {dist}m in {HitBone}");
LogToFile("SimpleKillFeed", sfkLog.ToString(), this);
}
private void OnSuicide(BasePlayer victim) => _killQueue.OnDeath(victim , null , string.Format(_("MsgFeedKillSuicide") , SanitizeName(GetClan(victim) + victim.displayName)));
private void OnKilledByRadiation(BasePlayer victim) => _killQueue.OnDeath(victim, null, string.Format(_("MsgFeedKillRadiation"), SanitizeName(GetClan(victim) + victim.displayName)));
private void OnKilledByEnt(BaseEntity attacker , BasePlayer victim) => _killQueue.OnDeath(victim , null , string.Format(_("MsgFeedKillEnt") , SanitizeName(GetClan(victim) + victim.displayName) , CustomEntName(attacker)));
private void OnKilledByBradley(BasePlayer victim) => _killQueue.OnDeath(victim, null, string.Format(_("MsgFeedKillBrad"), SanitizeName(GetClan(victim) + victim.displayName)));
private void OnKilledByAnimal(BaseEntity attacker, BasePlayer victim) => _killQueue.OnDeath(victim, null, string.Format(_("MsgFeedKillAnimal"), SanitizeName(GetClan(victim) + victim.displayName), CustomAnimalName(attacker)));
private void DeathByFall(BasePlayer victim) => _killQueue.OnDeath(victim, null, string.Format(_("MsgFeedKillFall"), SanitizeName(GetClan(victim) + victim.displayName)));
private void OnDeathFromWounds(BasePlayer attacker, BasePlayer victim) {
if (configdata.EnableChatFeed) {
if (!_data.DisabledUsers.Contains(attacker.userID) && attacker.userID.IsSteamId()) Player.Message(attacker, _("MsgAtkWounded", attacker), null, configdata.IconId, GetClan(victim) + victim.displayName);
if (!_data.DisabledUsers.Contains(victim.userID)) Player.Message(victim, _("MsgVictimWounded", victim), null, configdata.IconId, GetClan(attacker) + attacker.displayName);
}
_killQueue.OnDeath(victim, attacker, string.Format(_("MsgFeedWounded"), SanitizeName(GetClan(attacker) + attacker.displayName), SanitizeName(GetClan(victim) + victim.displayName)));
if (!configdata.EnableLogging) return;
var sfkLog = new StringBuilder($"{DateTime.Now}: ({attacker.UserIDString}){attacker.displayName} finished ({victim.UserIDString}){victim.displayName}");
LogToFile("SimpleKillFeed", sfkLog.ToString(), this);
}
private void OnBurned(BasePlayer attacker, BasePlayer victim) {
if (victim.IsNpc) {
if (!configdata.EnableNpcFeed) return;
var npc = victim;
_killQueue.OnDeath(victim, attacker, string.Format(_("MsgFeedBurned"), SanitizeName(GetClan(attacker) + attacker.displayName), CustomNpcName(npc)));
}
if (attacker.IsNpc) {
if (!configdata.EnableNpcFeed) return;
var npc = attacker;
_killQueue.OnDeath(victim, attacker, string.Format(_("MsgFeedBurned"), CustomNpcName(npc), SanitizeName(GetClan(victim) + victim.displayName)));
}
if (configdata.EnableChatFeed) {
if (!_data.DisabledUsers.Contains(attacker.userID)) Player.Message(attacker, _("MsgAtkBurned", attacker), null, configdata.IconId, GetClan(victim) + victim.displayName);
if (!_data.DisabledUsers.Contains(victim.userID)) Player.Message(victim, _("MsgVictimBurned", victim), null, configdata.IconId, GetClan(attacker) + attacker.displayName);
}
_killQueue.OnDeath(victim, attacker, string.Format(_("MsgFeedBurned"), SanitizeName(GetClan(attacker) + attacker.displayName), SanitizeName(GetClan(victim) + victim.displayName)));
if (!configdata.EnableLogging) return;
var sfkLog = new StringBuilder($"{DateTime.Now}: ({attacker.UserIDString}){attacker.displayName} burned ({victim.UserIDString}){victim.displayName}");
LogToFile("SimpleKillFeed", sfkLog.ToString(), this);
}
private void OnDrowning(BasePlayer victim) => _killQueue.OnDeath(victim , null , string.Format(_("MsgFeedKillDrowned") , SanitizeName(GetClan(victim) + victim.displayName)));
private void OnFrozen(BasePlayer victim) => _killQueue.OnDeath(victim , null , string.Format(_("MsgFeedKillCold") , SanitizeName(GetClan(victim) + victim.displayName)));
private void OnHunger(BasePlayer victim) => _killQueue.OnDeath(victim , null , string.Format(_("MsgFeedKillHunger") , SanitizeName(GetClan(victim) + victim.displayName)));
private void OnShock(BasePlayer victim) => _killQueue.OnDeath(victim , null , string.Format(_("MsgFeedKillShock") , SanitizeName(GetClan(victim) + victim.displayName)));
private void OnSentry(BasePlayer victim) => _killQueue.OnDeath(victim , null , string.Format(_("MsgFeedKillSentry") , SanitizeName(GetClan(victim) + victim.displayName)));
private void OnCactus(BasePlayer victim) => _killQueue.OnDeath(victim , null , string.Format(_("MsgFeedKillCactus") , SanitizeName(GetClan(victim) + victim.displayName)));
#endregion
#region UI
private class KillEvent : Pool.IPooled {
public int DisplayUntil;
public string Text;
public KillEvent Init(string text, int displayUntil) {
Text = text;
DisplayUntil = displayUntil;
return this;
}
public void EnterPool() {
Text = null;
DisplayUntil = 0;
}
public void LeavePool() {
}
}
private class KillQueue : MonoBehaviour {
private readonly WaitForSeconds _secondDelay = new WaitForSeconds(1f);
private readonly Queue<KillEvent> _queue = new Queue<KillEvent>(configdata.MaxFeedMessages);
private readonly CuiOutlineComponent _outlineStatic = new CuiOutlineComponent {
Distance = configdata.OutlineSize , Color = "0 0 0 1"
}
;
private readonly CuiRectTransformComponent[] _rectTransformStatic = new CuiRectTransformComponent[configdata.MaxFeedMessages];
private readonly CuiTextComponent[] _textStatic = new CuiTextComponent[configdata.MaxFeedMessages];
private readonly CuiElementContainer _cui = new CuiElementContainer();
private bool _needsRedraw;
private int _currentlyDrawn;
private SimpleKillFeed _plugin;
//add the plugin reference
public void SetPlugin(SimpleKillFeed plugin) {
_plugin = plugin;
}
public void OnDeath(BasePlayer victim , BasePlayer attacker , string text) {
Interface.Call("OnKillFeedMessageReceived" , text);
if (_queue.Count == configdata.MaxFeedMessages) DequeueEvent(_queue.Dequeue());
PushEvent(Pool.Get<KillEvent>().Init(text , Epoch.Current + configdata.FeedMessageTtlSec));
}
private void PushEvent(KillEvent evt) {
_queue.Enqueue(evt);
_needsRedraw = true;
DoProccessQueue();
}
private void Start() {
for (var i = 0; i < configdata.MaxFeedMessages; i++) {
_rectTransformStatic[i] = new CuiRectTransformComponent {
AnchorMax = $"{configdata.AnchorMax.Split(Convert.ToChar(' '))[0]} {float.Parse(configdata.AnchorMax.Split(Convert.ToChar(' '))[1]) - (configdata.HeightIdent * i)}" , AnchorMin = $"{configdata.AnchorMin.Split(Convert.ToChar(' '))[0]} {float.Parse(configdata.AnchorMin.Split(Convert.ToChar(' '))[1]) - (configdata.HeightIdent * i)}"
}
;
if (configdata.TextAnchor == "right") _textStatic[i] = new CuiTextComponent {
Align = TextAnchor.MiddleRight , FontSize = configdata.FontSize , Text = string.Empty
}
;
else if (configdata.TextAnchor == "left") _textStatic[i] = new CuiTextComponent {
Align = TextAnchor.MiddleLeft , FontSize = configdata.FontSize , Text = string.Empty
}
;
else _textStatic[i] = new CuiTextComponent {
Align = TextAnchor.MiddleRight , FontSize = configdata.FontSize , Text = string.Empty
}
;
}
StartCoroutine(ProccessQueue());
}
private void DequeueEvent(KillEvent evt) {
Pool.Free(ref evt);
_needsRedraw = true;
}
private void DoProccessQueue() {
while (_queue.Count > 0 && _queue.Peek().DisplayUntil < Epoch.Current) DequeueEvent(_queue.Dequeue());
if (!_needsRedraw) return;
var toBeRemoved = _currentlyDrawn;
_currentlyDrawn = 0;
foreach (var killEvent in _queue) {
var cuiText = _textStatic[_currentlyDrawn];
cuiText.Text = killEvent.Text;
_cui.Add(new CuiElement {
Name = $"kf-{_currentlyDrawn}" , Parent = "Under" , Components = {
cuiText, _rectTransformStatic[_currentlyDrawn], _outlineStatic
}
}
);
if (++_currentlyDrawn == configdata.MaxFeedMessages) break;
}
_needsRedraw = false;
SendKillCui(_cui , toBeRemoved);
_cui.Clear();
}
private IEnumerator ProccessQueue() {
while (!Interface.Oxide.IsShuttingDown) {
DoProccessQueue();
yield return _secondDelay;
}
}
//Updated this method with plugin reference
private void SendKillCui(CuiElementContainer cui , int toBeRemoved) {
var json = cui.ToJson();
foreach (var plr in BasePlayer.activePlayerList) {
// Check for Exclude Permission
if (_plugin.permission.UserHasPermission(plr.UserIDString , Exclude_Perm)) {
// User has the permission, remove existing UI, and skip adding new UI
for (var i = toBeRemoved; i > 0; i--) CuiHelper.DestroyUi(plr , $"kf-{i - 1}");
continue;
// Skip the rest of this loop and go to the next player
}
//If not excluded remove ui and add ui
for (var i = toBeRemoved; i > 0; i--) CuiHelper.DestroyUi(plr , $"kf-{i - 1}");
if (!_data.DisabledUsers.Contains(plr.userID)) CuiHelper.AddUi(plr , json);
}
}
public static void RemoveKillCui(string name) {
foreach (var plr in BasePlayer.activePlayerList) CuiHelper.DestroyUi(plr , name);
}
}
#endregion
#region Helpers
private string _(string msg, BasePlayer player = null) => lang.GetMessage(msg, this, player?.UserIDString);
private string GetCustomWeaponName(HitInfo hitInfo) {
var name = GetWeaponName(hitInfo);
if (string.IsNullOrEmpty(name)) return null;
string translatedName;
if (configdata.Weapons.TryGetValue(name, out translatedName)) return translatedName;
configdata.Weapons.Add(name, name);
Config.WriteObject(configdata);
return name;
}
private string CustomNpcName(BasePlayer npc) {
var name = npc.ShortPrefabName;
if (string.IsNullOrEmpty(name)) return null;
if (npc.displayName != npc.userID.ToString()) return npc.displayName;
string translatedName;
if (configdata.Npcs.TryGetValue(name, out translatedName)) return translatedName;
configdata.Npcs.Add(name, name);
Config.WriteObject(configdata);
return name;
}
private string CustomEntName(BaseEntity attacker) {
var name = attacker.ShortPrefabName;
if (string.IsNullOrEmpty(name)) return null;
string translatedName;
if (configdata.Ents.TryGetValue(name, out translatedName)) return translatedName;
configdata.Npcs.Add(name, name);
Config.WriteObject(configdata);
return name;
}
private string CustomAnimalName(BaseEntity attacker) {
var name = attacker.ShortPrefabName;
if (string.IsNullOrEmpty(name)) return null;
string translatedName;
if (configdata.Animal.TryGetValue(name, out translatedName)) return translatedName;
configdata.Npcs.Add(name, name);
Config.WriteObject(configdata);
return name;
}
private string GetWeaponName(HitInfo hitInfo) {
var _WeaponName = "??Unknown??";
if (hitInfo.Weapon == null) {
if (hitInfo.WeaponPrefab.prefabID == 3898309212) _WeaponName = "C4";
//3898309212
if (hitInfo.WeaponPrefab.prefabID == 3046924118) _WeaponName = "Rocket";
//3046924118
if (hitInfo.WeaponPrefab.prefabID == 1217937936) _WeaponName = "HV Rocket";
//1217937936
if (hitInfo.WeaponPrefab.prefabID == 2742759844) _WeaponName = "Satchel";
//1217937936
if (hitInfo.WeaponPrefab.prefabID == 2144399804) _WeaponName = "Beancan";
//2144399804
if (hitInfo.WeaponPrefab.prefabID == 1859672190) _WeaponName = "Shell";
//1217937936
if (hitInfo.WeaponPrefab.prefabID == 1128089209) _WeaponName = "Grenade";
//1128089209
if (hitInfo.WeaponPrefab.prefabID == 3717106868) _WeaponName = "Flamethrower";
//3717106868
if (hitInfo.WeaponPrefab.prefabID == 3032863244) _WeaponName = "Cannon Shell";
//3032863244
if (hitInfo.Initiator is GunTrap) _WeaponName = "GunTrap";
if (hitInfo.Initiator is FlameTurret) _WeaponName = "FlameTurret";
if (hitInfo.Initiator is AutoTurret) _WeaponName = "AutoTurret";
else if (hitInfo.damageTypes.GetMajorityDamageType() == DamageType.Heat) _WeaponName = "Fire";
else if (hitInfo.damageTypes.GetMajorityDamageType() == DamageType.Fun_Water) _WeaponName = "WaterGun";
if (hitInfo.WeaponPrefab.ToString().Contains("car_")) _WeaponName = "Car";
//Puts(hitInfo.WeaponPrefab.prefabID.ToString());
return _WeaponName;
}
if (hitInfo.Weapon != null) {
var _Weapon = hitInfo.Weapon;
var item = _Weapon.GetItem();
if (item != null) _WeaponName = item.info.displayName.english;
if (hitInfo.WeaponPrefab.prefabID == 1233562048) _WeaponName = "Grenade Launcher";
}
if (hitInfo.Initiator is GunTrap) _WeaponName = "GunTrap";
if (hitInfo.Initiator is FlameTurret) _WeaponName = "FlameTurret";
if (hitInfo.Initiator is AutoTurret) _WeaponName = "AutoTurret";
if (hitInfo.Initiator is HotAirBalloon) _WeaponName = "Hot Air Balloon";
if (hitInfo.WeaponPrefab is MLRSRocket) _WeaponName = "MLRS";
return _WeaponName;
}
private static bool IsExplosion(HitInfo hit) => (hit.WeaponPrefab != null && (hit.WeaponPrefab.ShortPrefabName.Contains("grenade") || hit.WeaponPrefab.ShortPrefabName.Contains("explosive"))) || hit.damageTypes.GetMajorityDamageType() == DamageType.Explosion || (!hit.damageTypes.IsBleedCausing() && hit.damageTypes.Has(DamageType.Explosion));
private static bool IsFlame(HitInfo hit) => hit.damageTypes.GetMajorityDamageType() == DamageType.Heat || (!hit.damageTypes.IsBleedCausing() && hit.damageTypes.Has(DamageType.Heat));
private static bool IsRadiation(HitInfo hit) => hit.damageTypes.GetMajorityDamageType() == DamageType.Radiation || (!hit.damageTypes.IsBleedCausing() && hit.damageTypes.Has(DamageType.Radiation));
private static bool IsCold(HitInfo hit) => hit.damageTypes.GetMajorityDamageType() == DamageType.Cold || (!hit.damageTypes.IsBleedCausing() && hit.damageTypes.Has(DamageType.Cold));
private static bool IsHunger(HitInfo hit) => hit.damageTypes.GetMajorityDamageType() == DamageType.Hunger || (!hit.damageTypes.IsBleedCausing() && hit.damageTypes.Has(DamageType.Hunger));
private static bool IsThirst(HitInfo hit) => hit.damageTypes.GetMajorityDamageType() == DamageType.Thirst || (!hit.damageTypes.IsBleedCausing() && hit.damageTypes.Has(DamageType.Thirst));
private static bool IsDrowning(HitInfo hit) => hit.damageTypes.GetMajorityDamageType() == DamageType.Drowned /* || (!hit.damageTypes.IsBleedCausing() && hit.damageTypes.Has(DamageType.Drowned))*
// ... [Code preview truncated due to length. Full source in attached file] ...