using Oxide.Core; using UnityEngine; using Rust; using System; using System.Collections.Generic; using Oxide.Core.Plugins; namespace Oxide.Plugins { [Info("ReflectDamage", "Chernarust", "1.6.0")] [Description("Reflects a portion of damage back to players in PvP, doubles headshot damage, 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; private bool applyBleeding; private bool enableDamageToVictim; private HashSet currentlyProcessing = new HashSet(); private HashSet playersInAbandonedBases = new HashSet(); private HashSet playersInRaidableBases = new HashSet(); [PluginReference] Plugin ZoneManager, DynamicPVP; public static DamageConfig _config; public class DamageConfig { public float bleedingIntensity = 5f; public float ReflectPercentage = 50f; public bool ApplyBleeding = true; public bool EnableDamageToVictim = true; public static DamageConfig DefaultConfig() { return new DamageConfig { bleedingIntensity = 5f, ReflectPercentage = 50f, ApplyBleeding = true, EnableDamageToVictim = true }; } } protected override void LoadDefaultConfig() => _config = DamageConfig.DefaultConfig(); protected override void LoadConfig() { base.LoadConfig(); try { _config = Config.ReadObject(); if (_config == null) LoadDefaultConfig(); SaveConfig(); } catch (Exception e) { Debug.LogException(e); PrintWarning("Creating new config file."); LoadDefaultConfig(); } } protected override void SaveConfig() => Config.WriteObject(_config); private void Init() { permission.RegisterPermission(permissionName, this); LoadConfig(); reflectPercentage = _config.ReflectPercentage; applyBleeding = _config.ApplyBleeding; enableDamageToVictim = _config.EnableDamageToVictim; } private void OnEntityTakeDamage(BasePlayer victim, HitInfo info) { CanEntityTakeDamage(victim, info); } private object CanEntityTakeDamage(BasePlayer victim, HitInfo info) { if (info == null || victim == null) return null; var attacker = info.InitiatorPlayer; if (attacker == null || victim == attacker || !IsRealPlayer(victim) || !IsRealPlayer(attacker)) return null; if (!permission.UserHasPermission(attacker.UserIDString, permissionName)) return null; // allow damage with TruePVE if (currentlyProcessing.Contains(attacker) || currentlyProcessing.Contains(victim)) return true; if (IsPlayerInZoneManagerZone(victim) && IsPlayerInZoneManagerZone(attacker)) return true; if (IsPlayerInDynamicZone(victim) && IsPlayerInDynamicZone(attacker)) return true; if (IsPlayerInRaidableZone(victim) && IsPlayerInRaidableZone(attacker)) return null; try { currentlyProcessing.Add(victim); currentlyProcessing.Add(attacker); // Double the damage if it's a headshot if (info.HitBone == 698017942) // 698017942 is the bone ID for the head in Rust { info.damageTypes.ScaleAll(2.0f); } // Reflect damage back to the attacker, using the percentage as a fraction float damageToReflect = info.damageTypes.Total() * (reflectPercentage / 100f); attacker.Hurt(damageToReflect, DamageType.Generic, victim); // Apply bleeding effect if enabled if (applyBleeding) { attacker.metabolism.bleeding.Add(_config.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; return false; // block damage with TruePVE } return true; // allow damage with TruePVE } catch (Exception ex) { Puts($"Error in OnEntityTakeDamage: {ex.Message}"); } finally { currentlyProcessing.Remove(victim); currentlyProcessing.Remove(attacker); } return null; // can be true or false or null } private bool IsPlayerInZoneManagerZone(BasePlayer player) { if (ZoneManager == null) { Puts("ZoneManager plugin reference is null."); return false; } string[] zoneIDs = (string[])ZoneManager?.Call("GetPlayerZoneIDs", player); if (zoneIDs != null) { foreach (string zoneID in zoneIDs) { if ((bool)ZoneManager?.Call("IsPlayerInZone", zoneID, player) == true) return true; } } return false; } private bool IsPlayerInDynamicZone(BasePlayer player) { if (DynamicPVP == null) { Puts("DynamicPVP plugin reference is null."); return false; } string[] dynamicPVPZones = (string[])DynamicPVP?.Call("AllDynamicPVPZones"); if (dynamicPVPZones == null) { Puts("dynamicPVPZones is null."); return false; } foreach (string zoneID in dynamicPVPZones) { if ((bool)DynamicPVP?.Call("IsDynamicPVPZone", zoneID) == true && (bool)DynamicPVP?.Call("IsPlayerInPVPDelay", player.userID) == true) return true; } return false; } private bool IsPlayerInRaidableZone(BasePlayer player) { // Check if player is in an abandoned base/raidable base return playersInAbandonedBases.Contains(player.userID) || playersInRaidableBases.Contains(player.userID); } private bool IsRealPlayer(BasePlayer player) => player.UserIDString.IsSteamId(); private void OnPlayerEnteredAbandonedBase(BasePlayer player) => playersInAbandonedBases.Add(player.userID); private void OnPlayerExitAbandonedBase(BasePlayer player) => playersInAbandonedBases.Remove(player.userID); private void OnPlayerEnteredRaidableBase(BasePlayer player) => playersInRaidableBases.Add(player.userID); private void OnPlayerExitedRaidableBase(BasePlayer player) => playersInRaidableBases.Remove(player.userID); } }