using Oxide.Core; using UnityEngine; using Rust; using System; using System.Collections.Generic; namespace Oxide.Plugins { [Info("ReflectDamage", "Chernarust", "1.0.0")] [Description("Reflects a portion of damage back to players in PvP and optionally applies a bleeding effect to the attacker. Requires specific permission.")] public class ReflectDamage : RustPlugin { private const string permissionName = "reflectdamage.use"; private float reflectPercentage = 50f; // Default: 50% damage reflection, using 1-100 scale private bool applyBleeding = true; private float bleedingIntensity = 5f; private bool enableDamageToVictim = true; private HashSet currentlyProcessing = new HashSet(); private HashSet abandonedBasePlayers = new HashSet(); // Store players in abandoned bases private void Init() { permission.RegisterPermission(permissionName, this); LoadConfigValues(); } protected override void LoadDefaultConfig() { PrintWarning("Creating a new configuration file."); Config.Clear(); Config["ReflectPercentage"] = reflectPercentage; Config["ApplyBleeding"] = applyBleeding; Config["BleedingIntensity"] = bleedingIntensity; Config["EnableDamageToVictim"] = enableDamageToVictim; SaveConfig(); } private void LoadConfigValues() { reflectPercentage = GetConfig("ReflectPercentage", 50f); applyBleeding = GetConfig("ApplyBleeding", true); bleedingIntensity = GetConfig("BleedingIntensity", 5f); enableDamageToVictim = GetConfig("EnableDamageToVictim", true); } private T GetConfig(string key, T defaultValue) { if (Config[key] == null) { Config[key] = defaultValue; SaveConfig(); return defaultValue; } if (typeof(T) == typeof(float)) { float floatValue; if (float.TryParse(Config[key].ToString(), out floatValue)) { return (T)Convert.ChangeType(floatValue, typeof(T)); } } else if (typeof(T) == typeof(bool)) { bool boolValue; if (bool.TryParse(Config[key].ToString(), out boolValue)) { return (T)Convert.ChangeType(boolValue, typeof(T)); } } return (T)Convert.ChangeType(Config[key], typeof(T)); } void OnEntityTakeDamage(BaseCombatEntity entity, HitInfo info) { if (entity == null || info == null || !(entity is BasePlayer victim)) return; var attacker = info.InitiatorPlayer; if (attacker == null || victim == attacker || !IsRealPlayer(victim) || !IsRealPlayer(attacker) || IsZombieHorde(victim) || IsZombieHorde(attacker)) return; // Check if the attacker is in an abandoned base if (abandonedBasePlayers.Contains(attacker.userID)) return; if (!permission.UserHasPermission(attacker.UserIDString, permissionName)) return; if (currentlyProcessing.Contains(attacker) || currentlyProcessing.Contains(victim)) return; try { currentlyProcessing.Add(victim); // Add victim to processing to prevent recursion currentlyProcessing.Add(attacker); // Add attacker to processing to prevent recursion // Reflect damage back to the attacker, using the percentage as a fraction float damageToReflect = info.damageTypes.Total() * (reflectPercentage / 100f); if (info.isHeadshot) // Check if it's a headshot { damageToReflect *= 2f; // Double damage for headshots } attacker.Hurt(damageToReflect, DamageType.Generic, victim); // Apply bleeding effect if enabled if (applyBleeding) { var currentBleeding = attacker.metabolism.bleeding.value; if (currentBleeding < 0.8f) // Assuming 1.0 is the max value { attacker.metabolism.bleeding.Add(bleedingIntensity); } } // Cancel the damage to the victim if configured if (!enableDamageToVictim) { info.damageTypes.ScaleAll(0); info.HitEntity = null; info.DoHitEffects = false; info.PointStart = Vector3.zero; info.PointEnd = Vector3.zero; } } catch (Exception ex) { Puts($"Error in OnEntityTakeDamage: {ex.Message}"); } finally { currentlyProcessing.Remove(victim); currentlyProcessing.Remove(attacker); } } private bool IsRealPlayer(BasePlayer player) => !player.IsNpc && player.UserIDString.IsSteamId(); private static bool IsZombieHorde(BasePlayer player) => player.GetType().Name.Equals("ZombieNPC"); // Hook to handle players entering abandoned bases void OnPlayerEnteredAbandonedBase(BasePlayer player) { if (player != null) { abandonedBasePlayers.Add(player.userID); } } // Hook to handle players exiting abandoned bases void OnPlayerExitAbandonedBase(BasePlayer player) { if (player != null) { abandonedBasePlayers.Remove(player.userID); } } } }