C#:
// Все права принадлежат дискорд сообществу https://discord.gg/VgNHPpNrz6
#region License (GPL v2)
/*
NextGenPVE - Prevent damage to players and objects in a Rust PVE environment
Copyright (c) 2020-2025 RFC1920 <desolationoutpostpve@gmail.com>
This program is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License v2.0.This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.See the
GNU General Public License for more details.You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.Optionally you can also view the license at <http://www.gnu.org/licenses/>.*/
#endregion License (GPL v2)
// Reference: System.Data.SQLite
// Reference: System.Net.Http
using Newtonsoft.Json;
using Oxide.Core;
using Oxide.Core.Configuration;
using Oxide.Core.Libraries.Covalence;
using Oxide.Core.Plugins;
using Oxide.Game.Rust.Cui;
using System;
using System.Collections.Generic;
using System.Data.SQLite;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Net.Http;
using System.Text;
using System.Text.RegularExpressions;
using UnityEngine;
namespace Oxide.Plugins {
[Info("NextGen PVE", "RFC1920", "1.7.8")]
[Description("Prevent damage to players and objects in a PVE environment")]
internal class NextGenPVE : RustPlugin {
#region vars
// Ruleset to multiple zones
private Dictionary<string, NextGenPVEZoneMap> ngpvezonemaps = new();
private Dictionary<int, NGPVE_Schedule> ngpveschedule = new();
private Dictionary<string, long> lastConnected = new();
private Dictionary<ulong, DateTime> playerShotHeli = new();
private const string permNextGenPVEUse = "nextgenpve.use";
private const string permNextGenPVEAdmin = "nextgenpve.admin";
private const string permNextGenPVEGod = "nextgenpve.god";
private ConfigData configData;
private SQLiteConnection sqlConnection;
private string connStr;
private bool newsave;
public Dictionary<string, List<string>> steamFriends = new();
private List<ulong> isopen = new();
private readonly string BannerColor = "Grey";
private string TextColor = "Red";
[PluginReference]
private readonly Plugin ZoneManager, Humanoids, HumanNPC, Friends, Clans, GUIAnnouncements, NREHook, RustIO;
private readonly string logfilename = "log";
private bool dolog;
private bool onfoundnre;
private bool enabled;
private bool inPurge;
private bool purgeLast;
private Timer scheduleTimer;
private Timer heliTimer;
private const string NGPVEPLUGINS = "nextgenpve.plugins";
private const string NGPVERULELIST = "nextgenpve.rulelist";
private const string NGPVEEDITRULESET = "nextgenpve.ruleseteditor";
private const string NGPVERULEEDIT = "nextgenpve.ruleeditor";
private const string NGPVEENTEDIT = "nextgenpve.enteditor";
private const string NGPVEVALUEEDIT = "nextgenpve.value";
private const string NGPVESCHEDULEEDIT = "nextgenpve.schedule";
private const string NGPVEENTSELECT = "nextgenpve.selectent";
private const string NGPVENPCSELECT = "nextgenpve.selectnpc";
private const string NGPVERULESELECT = "nextgenpve.selectrule";
private const string NGPVERULEEXCLUSIONS = "nextgenpve.exclusions";
private const string NGPVECUSTOMSELECT = "nextgenpve.customsel";
#endregion
#region Message
private string Lang(string key, string id = null, params object[] args) => string.Format(lang.GetMessage(key, this, id), args);
private void Message(IPlayer player, string key, params object[] args) => player.Message(Lang(key, player.Id, args));
private void LMessage(IPlayer player, string key, params object[] args) => player.Reply(Lang(key, player.Id, args));
#endregion
#region init
private void Init() {
DynamicConfigFile dataFile = Interface.GetMod().DataFileSystem.GetDatafile(Name + "/nextgenpve");
dataFile.Save();
connStr = "Data Source=" + Regex.Escape(Path.Combine(Interface.GetMod().DataDirectory, Name)) + Path.DirectorySeparatorChar + "nextgenpve.db";
LoadConfigVariables();
AddCovalenceCommand("pveupdate", "CmdUpdateEnts");
AddCovalenceCommand("pvebackup", "CmdNextGenPVEbackup");
AddCovalenceCommand("pverule", "CmdNextGenPVEGUI");
AddCovalenceCommand("pveenable", "CmdNextGenPVEenable");
AddCovalenceCommand("pvedrop", "CmdNextGenPVEDrop");
AddCovalenceCommand("pvelog", "CmdNextGenPVElog");
AddCovalenceCommand("pvedebug", "CmdNextGenPVEDebug");
AddCovalenceCommand("pvereload", "CmdNextGenPVEReadCfg");
permission.RegisterPermission(permNextGenPVEUse, this);
permission.RegisterPermission(permNextGenPVEAdmin, this);
permission.RegisterPermission(permNextGenPVEGod, this);
}
private void OnServerInitialized() {
ConsoleSystem.Run(ConsoleSystem.Option.Server.FromServer(), "server.pve 0");
sqlConnection = new SQLiteConnection(connStr);
sqlConnection.Open();
if (NREHook && configData.Options.enableDebugOnErrors) {
Puts("NREHook is installed.Will enable debugging if NRE is fired.");
}
LoadData();
if (newsave) {
Puts("Wipe detected.Clearing zone maps and setting purge schedule, if enabled.");
ngpvezonemaps = new Dictionary<string, NextGenPVEZoneMap>();
newsave = false;
NextTick(() => {
NewPurgeSchedule();
SaveData();
UpdateEnts();
}
);
}
const string query = "CREATE INDEX IF NOT EXISTS ruleset_name_idx ON ngpve_rulesets (name, zone, damage, enabled, exception);" + "CREATE INDEX IF NOT EXISTS rules_idx ON ngpve_rules (name, source, target);" + "CREATE INDEX IF NOT EXISTS ent_idx ON ngpve_entities (name, type);REINDEX;";
using (SQLiteCommand us = new(query, sqlConnection)) {
us.ExecuteNonQuery();
}
if (configData.Options.useSteam && configData.Options.steamApiKey?.Length > 0) {
foreach (BasePlayer player in BasePlayer.activePlayerList) {
GetSteamFriends(player);
}
}
enabled = true;
if (configData.Options.useSchedule) RunSchedule(true);
HeliTimer();
}
private void HeliTimer() {
if (!configData.Options.UseHeliTimerToDetectPlayer) return;
if (configData.Options.debug) Puts("Checking playerShotHeli");
float heliminutes = configData.Options.heliTimerMinutes > 0 ? configData.Options.heliTimerMinutes : 5;
heliTimer = timer.Once(60, () => HeliTimer());
if (playerShotHeli.Count == 0) {
return;
}
foreach (KeyValuePair<ulong, DateTime> ht in new Dictionary<ulong, DateTime>(playerShotHeli)) {
if (configData.Options.debug) Puts($"Player {ht.Key} found in playerShotHeli.");
if (ht.Value.AddMinutes(heliminutes) >= DateTime.Now) {
playerShotHeli.Remove(ht.Key);
if (configData.Options.debug) Puts($"Player {ht.Key} removed from playerShotHeli list.");
}
}
}
private void OnNewSave() => newsave = true;
private void OnPlayerConnected(BasePlayer player) {
if (configData.Options.useSteam && configData.Options.steamApiKey?.Length > 0) GetSteamFriends(player);
}
private void OnUserConnected(IPlayer player) => OnUserDisconnected(player);
private void OnUserDisconnected(IPlayer player) {
if (player == null) return;
try {
if (!player.Id.IsSteamId()) return;
}
catch {
return;
}
lastConnected.TryGetValue(player?.Id, out long lc);
if (lc > 0) {
lastConnected[player?.Id] = ToEpochTime(DateTime.UtcNow);
}
else {
lastConnected.Add(player?.Id, ToEpochTime(DateTime.UtcNow));
}
SaveData();
}
private object OnServerCommand(ConsoleSystem.Arg arg) {
return OnPlayerCommand(arg.Player(), arg.cmd.FullName, arg.Args);
}
private object OnPlayerCommand(BasePlayer player, string command, string[] args) {
if (player != null) {
if (isopen.Contains(player.userID) && !command.StartsWith("pve")) {
Puts($"Player tried to run command while gui open: {command}");
return true;
}
}
return null;
}
private void UpdateEnts() {
// Populate the entities table with any new entities (Typically only at wipe but can be run manually via pveupdate.)
// All new ents are added as unknown for manual recategorization (except those containing 'npc').Puts("Finding new entity types...");
List<string> names = new();
foreach (UnityEngine.Object obj in Resources.FindObjectsOfTypeAll(new BaseCombatEntity().GetType())) {
string objname = obj?.GetType().ToString();
if (string.IsNullOrEmpty(objname)) continue;
if (objname.Contains("Entity")) continue;
if (names.Contains(objname)) continue;
// Saves 20-30 seconds of processing time.string category = objname.Contains("npc", CompareOptions.OrdinalIgnoreCase) ? "npc" : "unknown";
names.Add(objname);
//Puts($"Checking {objname}");
using SQLiteConnection c = new(connStr);
c.Open();
string query = $"INSERT INTO ngpve_entities (name, type) SELECT '{category}', '{objname}' WHERE NOT EXISTS (SELECT * FROM ngpve_entities WHERE type='{objname}')";
using SQLiteCommand us = new(query, c);
if (us.ExecuteNonQuery() > 0) {
Puts($"Added {objname} as {category}.");
}
}
foreach (UnityEngine.Object obj in Resources.FindObjectsOfTypeAll(new GrenadeWeapon().GetType())) {
string objname = obj?.GetType().ToString();
if (string.IsNullOrEmpty(objname)) continue;
if (objname.Contains("Entity")) continue;
if (names.Contains(objname)) continue;
// Saves 20-30 seconds of processing time.string category = objname.Contains("npc", CompareOptions.OrdinalIgnoreCase) ? "npc" : "grenade";
names.Add(objname);
using SQLiteConnection c = new(connStr);
c.Open();
string query = $"INSERT INTO ngpve_entities (name, type) SELECT '{category}', '{objname}' WHERE NOT EXISTS (SELECT * FROM ngpve_entities WHERE type='{objname}')";
using SQLiteCommand us = new(query, c);
if (us.ExecuteNonQuery() > 0) {
Puts($"Added {objname} as {category}.");
}
}
Puts("Done!");
CleanupEnts();
}
private void CleanupEnts() {
// Find things defined as unknown but also in another category and delete the unknowns.// name is category, type is ObjectName
using SQLiteConnection c = new(connStr);
c.Open();
List<string> types = new();
List<string> toremove = new();
using (SQLiteCommand us = new("SELECT name, type FROM ngpve_entities ORDER BY name", c)) using (SQLiteDataReader rentry = us.ExecuteReader()) {
while (rentry.Read()) {
string name = rentry.GetString(0);
string type = rentry.GetString(1);
if (types.Contains(type) && name == "unknown") {
toremove.Add(type);
continue;
}
if (!types.Contains(type)) types.Add(type);
}
}
if (toremove.Count > 0) {
Puts("Cleaning up entity types...");
string q = $"DELETE FROM ngpve_entities WHERE name='unknown' AND type in ('{string.Join("','", toremove)}')";
using (SQLiteCommand us = new(q, c)) {
us.ExecuteNonQuery();
}
Puts("Done!");
}
}
protected override void LoadDefaultMessages() {
lang.RegisterMessages(new Dictionary<string, string> {
["notauthorized"]
= "You don't have permission to use this command.", ["zonemanagerreq"] = "ZoneManager required for handling multiple active rulesets.", ["nextgenpverulesets"] = "NextGenPVE Rulesets", ["nextgenpverulesetsf"] = "NextGenPVE Rulesets and Flags", ["rulesets"] = "Rulesets", ["pveenabled"] = "PVE Enabled for {0} ruleset.", ["pvedisabled"] = "PVE Disabled for {0} ruleset.", ["nextgenpveruleset"] = "NextGenPVE Ruleset", ["nextgenpverule"] = "NextGenPVE Rule", ["nextgenpveruleselect"] = "NextGenPVE Rule Select", ["nextgenpveentselect"] = "NextGenPVE Entity Select", ["nextgenpvenpcselect"] = "NextGenPVE NPC Turret Target Exclusions", ["npcselectnotes"] = "Selected (Disabled) items in orange will be excluded from targeting by turrets, etc.Items in green will be targeted (Enabled).Use the Global Flags as a reference.", ["nextgenpveentedit"] = "NextGenPVE Entity Edit", ["nextgenpvevalue"] = "NextGenPVE Ruleset Value", ["nextgenpveexclusions"] = "NextGenPVE Ruleset Exclusions", ["nextgenpveschedule"] = "NextGenPVE Ruleset Schedule", ["scheduling"] = "Schedules should be in the format of 'day(s);starttime;endtime'.Use * for all days.Enter 0 to clear.", ["current2chedule"] = "Currently scheduled for day: {0} from {1} until {2}", ["noschedule"] = "Not currently scheduled", ["activelevels"] = "Active Levels", ["ground"] = "Ground", ["tunnel"] = "Tunnel", ["tunneld"] = "Tunnel Depth", ["sky"] = "Sky", ["skys"] = "Sky Start Height", ["all"] = "All", ["flags"] = "Global Flags", ["defload"] = "Set Defaults", ["deflag"] = "Reset Flags", ["reload"] = "Reload Config", ["default"] = "default", ["drop"] = "RESET DATABASE", ["none"] = "none", ["close"] = "Close", ["clone"] = "Clone", ["allow"] = "Allow", ["block"] = "Block", ["save"] = "Save", ["edit"] = "Edit", ["delete"] = "Delete", ["new"] = "new", ["newcat"] = "New Collection", ["setcat"] = "Set Collection", ["clicktoedit"] = "^Click to Edit^", ["editname"] = "Edit Name", ["editnpc"] = "Edit NPC Targeting", ["standard"] = "Standard", ["automated"] = "Automated", ["add"] = "Add", ["all"] = "All", ["true"] = "True", ["false"] = "False", ["editing"] = "Editing", ["source"] = "Source", ["target"] = "Target", ["exclude"] = "Exclude from", ["enableset"] = "Enable set to {0}", ["enabled"] = "Enabled", ["disabled"] = "Disabled", ["genabled"] = "Globally Enabled", ["gdisabled"] = "Globally Disabled", ["zone"] = "Zone", ["purgeactive"] = "PURGE ACTIVE", ["purgeinactive"] = "PURGE IS OVER", ["sinvert"] = "Invert Schedule", ["sinverted"] = "Schedule Inverted", ["schedule"] = "Schedule", ["schedulep"] = "Schedule (Active period)", ["schedulei"] = "Schedule (Inactive period)", ["scheduled"] = "Schedule Disabled", ["editing"] = "Editing", ["select"] = "Select", ["damage"] = "Damage", ["stock"] = "Stock", ["custom"] = "Custom", ["customedit"] = "Rule/Ent Editor", ["lookup"] = "lookup", ["unknown"] = "Unknown", ["search"] = "Search", ["defaultdamage"] = "Default Damage", ["damageexceptions"] = "Damage Exceptions", ["sourceexcl"] = "Source Exclusions", ["targetexcl"] = "Target Exclusions", ["clicktosetcoll"] = "Click to set collection", ["rule"] = "Rule", ["rules"] = "Rules", ["entities"] = "Entities", ["uentities"] = "Unknown Entities", ["logging"] = "Logging set to {0}", ["debug"] = "Debug set to {0}", ["day"] = "Day", ["starthour"] = "Start Hour", ["endhour"] = "End Hour", ["startminute"] = "Start Minute", ["endminute"] = "End Minute", ["NPCAutoTurretTargetsPlayers"] = "NPC AutoTurret Targets Players", ["NPCAutoTurretTargetsNPCs"] = "NPC AutoTurret Targets NPCs", ["AutoTurretTargetsPlayers"] = "AutoTurret Targets Players", ["HeliTurretTargetsPlayers"] = "Heli Turret Targets Players", ["AutoTurretTargetsNPCs"] = "AutoTurret Targets NPCs", ["NPCSamSitesIgnorePlayers"] = "NPC SamSites Ignore Players", ["SamSitesIgnorePlayers"] = "SamSites Ignore Players", ["NPCSamSitesTargetPlayers"] = "(ext) NPC SamSites Target Players", ["SamSitesTargetPlayers"] = "(ext) SamSites Target Players", ["NPCSamSitesTargetNPCs"] = "(ext) NPC SamSites Target NPCs", ["SamSitesTargetNPCs"] = "(ext) SamSites Target NPCs", ["NPCSamSitesTargetAnimals"] = "(ext) NPC SamSites Target Animals", ["SamSitesTargetAnimals"] = "(ext) SamSites Target Animals", ["AllowSuicide"] = "Allow Player Suicide", ["AllowFriendlyFire"] = "Allow Friendly Fire", ["TrapsIgnorePlayers"] = "Traps Ignore Players", ["HonorBuildingPrivilege"] = "Honor Building Privilege", ["UnprotectedBuildingDamage"] = "Unprotected Building Damage", ["UseHeliTimerToDetectPlayer"] = "Use Heli Timer To Detect Player", ["TwigDamage"] = "Twig Damage", ["HonorRelationships"] = "Honor Relationships", ["backup"] = "Backup Database", ["BackupDone"] = "NextGenPVE database has been backed up to {0}", ["RestoreDone"] = "NextGenPVE database has been restored from {0}", ["RestoreFailed"] = "Unable to restore from {0}! Verify presence of file.", ["RestoreFilename"] = "Restore requires source filename ending in '.db'!", ["RestoreAvailable"] = "Available files:\n\t{0}"
}
, this, "en");
}
private void Unload() {
foreach (BasePlayer player in BasePlayer.activePlayerList) {
//if (!permission.UserHasPermission(player?.UserIDString, permNextGenPVEAdmin)) continue;
CuiHelper.DestroyUi(player, NGPVERULELIST);
CuiHelper.DestroyUi(player, NGPVERULEEDIT);
CuiHelper.DestroyUi(player, NGPVEVALUEEDIT);
CuiHelper.DestroyUi(player, NGPVESCHEDULEEDIT);
CuiHelper.DestroyUi(player, NGPVEEDITRULESET);
CuiHelper.DestroyUi(player, NGPVERULESELECT);
CuiHelper.DestroyUi(player, NGPVEENTSELECT);
CuiHelper.DestroyUi(player, NGPVENPCSELECT);
CuiHelper.DestroyUi(player, NGPVEENTEDIT);
CuiHelper.DestroyUi(player, NGPVERULEEXCLUSIONS);
CuiHelper.DestroyUi(player, NGPVECUSTOMSELECT);
}
scheduleTimer?.Destroy();
heliTimer?.Destroy();
sqlConnection?.Close();
}
private void LoadData() {
bool found = false;
using (SQLiteConnection c = new(connStr)) {
c.Open();
using SQLiteCommand r = new("SELECT name FROM sqlite_master WHERE type='table' AND name='ngpve_entities'", c);
using SQLiteDataReader rentry = r.ExecuteReader();
while (rentry.Read()) {
found = true;
}
}
if (!found) LoadDefaultEntities();
found = false;
using (SQLiteConnection c = new(connStr)) {
c.Open();
using SQLiteCommand r = new("SELECT name FROM sqlite_master WHERE type='table' AND name='ngpve_rules'", c);
using SQLiteDataReader rentry = r.ExecuteReader();
while (rentry.Read()) {
found = true;
}
}
if (!found) LoadDefaultRules();
found = false;
using (SQLiteConnection c = new(connStr)) {
c.Open();
using SQLiteCommand r = new("SELECT name FROM sqlite_master WHERE type='table' AND name='ngpve_rulesets'", c);
using SQLiteDataReader rentry = r.ExecuteReader();
while (rentry.Read()) {
found = true;
}
}
if (!found) LoadDefaultRuleset();
found = false;
using (SQLiteConnection c = new(connStr)) {
c.Open();
using SQLiteCommand r = new("SELECT name FROM sqlite_master WHERE type='table' AND name='ngpve_targetexclusion'", c);
using SQLiteDataReader rentry = r.ExecuteReader();
while (rentry.Read()) {
found = true;
}
}
if (!found) LoadNPCTgtExclDb();
ngpvezonemaps = Interface.GetMod().DataFileSystem.ReadObject<Dictionary<string, NextGenPVEZoneMap>>(Name + "/ngpve_zonemaps");
lastConnected = Interface.GetMod().DataFileSystem.ReadObject<Dictionary<string, long>>(Name + "/ngpve_lastconnected");
}
private void SaveData() {
Interface.GetMod().DataFileSystem.WriteObject(Name + "/ngpve_zonemaps", ngpvezonemaps);
Interface.GetMod().DataFileSystem.WriteObject(Name + "/ngpve_lastconnected", lastConnected);
}
#endregion
#region Oxide_hooks
private object OnTrapTrigger(BaseTrap trap, GameObject go) {
if (!enabled) return null;
BasePlayer player = go.GetComponent<BasePlayer>();
if (trap is null) return null;
if (player is null) return null;
object cantraptrigger = Interface.CallHook("CanEntityTrapTrigger", new object[] {
trap, player
}
);
if (cantraptrigger is object && cantraptrigger is bool && (bool)cantraptrigger) return null;
if (configData.Options.TrapsIgnorePlayers) return false;
return null;
}
private BasePlayer GetMountedPlayer(BaseMountable mount) {
if (mount.GetMounted()) {
return mount.GetMounted();
}
if (mount as BaseVehicle) {
BaseVehicle vehicle = mount as BaseVehicle;
foreach (BaseVehicle.MountPointInfo point in vehicle.mountPoints) {
if (point.mountable.IsValid() && point.mountable.GetMounted()) {
return point.mountable.GetMounted();
}
}
}
return null;
}
private object OnSamSiteTarget(SamSite sam, BaseMountable mountable) {
if (sam is null) return null;
if (mountable is null) return null;
BasePlayer player = GetMountedPlayer(mountable);
if (player.IsValid()) {
if (sam.OwnerID == 0 && configData.Options.NPCSamSitesIgnorePlayers) {
DoLog($"Skipping targeting by NPC SamSite of player {player.displayName}");
return true;
// block
}
else if (sam.OwnerID > 0 && configData.Options.SamSitesIgnorePlayers) {
DoLog($"Skipping targeting by SamSite of player {player.displayName}");
return true;
// block
}
try {
if (configData.Options.CanEntityBeTargeted?.Count > 0) {
foreach (string plugin in configData.Options.CanEntityBeTargeted) {
Plugin plug = plugins.Find(plugin);
object extCanEntityBeTargeted = plug?.Call("CanEntityBeTargeted");
if (extCanEntityBeTargeted is object && extCanEntityBeTargeted is bool && (bool)extCanEntityBeTargeted) {
return null;
}
}
}
else {
object extCanEntityBeTargeted = Interface.CallHook("CanEntityBeTargeted", new object[] {
player, sam
}
);
if (extCanEntityBeTargeted is object && extCanEntityBeTargeted is bool && (bool)extCanEntityBeTargeted) {
if ((bool)extCanEntityBeTargeted) {
return null;
// allow
}
else {
return true;
// block
}
}
}
}
catch {
if (configData.Options.debug) Puts("Humanoids detection failure.");
}
}
return null;
// allow
}
private object CanBeTargeted(BasePlayer target, GunTrap turret) {
if (target is null) return null;
if (turret is null) return null;
if (!enabled) return null;
try {
if (configData.Options.CanEntityBeTargeted?.Count > 0) {
foreach (string plugin in configData.Options.CanEntityBeTargeted) {
Plugin plug = plugins.Find(plugin);
object extCanEntityBeTargeted = plug?.Call("CanEntityBeTargeted");
if (extCanEntityBeTargeted is object && extCanEntityBeTargeted is bool && (bool)extCanEntityBeTargeted) {
return null;
}
}
}
else {
object extCanEntityBeTargeted = Interface.CallHook("CanEntityBeTargeted", new object[] {
target, turret
}
);
if (extCanEntityBeTargeted is object && extCanEntityBeTargeted is bool && (bool)extCanEntityBeTargeted) {
return null;
}
}
}
catch {
}
if (IsHumanoid(target) || IsHumanNPC(target) || target.IsNpc) {
if (!configData.Options.AutoTurretTargetsNPCs) return false;
}
else if (!configData.Options.AutoTurretTargetsPlayers) {
return false;
}
return null;
}
private object CanBeTargeted(BasePlayer target, FlameTurret turret) {
if (!enabled) return null;
if (target is null) return null;
if (turret is null) return null;
try {
if (configData.Options.CanEntityBeTargeted?.Count > 0) {
foreach (string plugin in configData.Options.CanEntityBeTargeted) {
Plugin plug = plugins.Find(plugin);
object extCanEntityBeTargeted = plug?.Call("CanEntityBeTargeted");
if (extCanEntityBeTargeted is object && extCanEntityBeTargeted is bool && (bool)extCanEntityBeTargeted) {
return null;
}
}
}
else {
object extCanEntityBeTargeted = Interface.CallHook("CanEntityBeTargeted", new object[] {
target, turret
}
);
if (extCanEntityBeTargeted is object && extCanEntityBeTargeted is bool && (bool)extCanEntityBeTargeted) {
return null;
}
}
}
catch {
}
if (IsHumanoid(target) || IsHumanNPC(target) || target.IsNpc) {
if (!configData.Options.AutoTurretTargetsNPCs) return false;
}
else if (!configData.Options.AutoTurretTargetsPlayers) {
return false;
}
return null;
}
private object CanBeTargeted(BasePlayer target, HelicopterTurret turret) {
if (!enabled) return null;
if (target is null) return null;
if (turret is null) return null;
if (!configData.Options.HeliTurretTargetsPlayers) return false;
return null;
}
private object CanBeTargeted(BasePlayer target, AutoTurret turret) {
if (!enabled) return null;
if (target is null) return null;
if (turret is null) return null;
try {
if (configData.Options.CanEntityBeTargeted?.Count > 0) {
foreach (string plugin in configData.Options.CanEntityBeTargeted) {
Plugin plug = plugins.Find(plugin);
object extCanEntityBeTargeted = plug?.Call("CanEntityBeTargeted");
if (extCanEntityBeTargeted is object && extCanEntityBeTargeted is bool && (bool)extCanEntityBeTargeted) {
return null;
}
}
}
else {
object extCanEntityBeTargeted = Interface.CallHook("CanEntityBeTargeted", new object[] {
target, turret
}
);
if (extCanEntityBeTargeted is object && extCanEntityBeTargeted is bool && (bool)extCanEntityBeTargeted) {
return null;
}
}
}
catch {
}
if (IsHumanoid(target) || IsHumanNPC(target) || target.IsNpc) {
if (!configData.Options.AutoTurretTargetsNPCs) return false;
}
else if (!configData.Options.AutoTurretTargetsPlayers) {
return false;
}
return null;
}
private object CanBeTargeted(BasePlayer target, NPCAutoTurret turret) {
if (!enabled) return null;
if (target is null) return null;
if (turret is null) return null;
try {
if (configData.Options.CanEntityBeTargeted?.Count > 0) {
foreach (string plugin in configData.Options.CanEntityBeTargeted) {
Plugin plug = plugins.Find(plugin);
object extCanEntityBeTargeted = plug?.Call("CanEntityBeTargeted");
if (extCanEntityBeTargeted is object && extCanEntityBeTargeted is bool && (bool)extCanEntityBeTargeted) {
return null;
}
}
}
else {
object extCanEntityBeTargeted = Interface.CallHook("CanEntityBeTargeted", new object[] {
target, turret
}
);
if (extCanEntityBeTargeted is object && extCanEntityBeTargeted is bool && (bool)extCanEntityBeTargeted) {
return null;
}
}
}
catch {
}
if (IsHumanoid(target) || IsHumanNPC(target) || target.IsNpc) {
if (!configData.Options.NPCAutoTurretTargetsNPCs) return false;
}
else if (!configData.Options.NPCAutoTurretTargetsPlayers) {
return false;
}
return null;
}
private bool BlockFallDamage(BaseCombatEntity entity) {
// Special case where attack by scrapheli initiates fall damage on a player.This was often used to kill players and bypass the rules.List<BaseEntity> ents = new();
Vis.Entities(entity.transform.position, 5, ents);
foreach (BaseEntity ent in ents) {
if (ent.ShortPrefabName == "scraptransporthelicopter" && configData.Options.BlockScrapHeliFallDamage) {
DoLog("Fall caused by scrapheli.Blocking...");
return true;
}
}
return false;
}
#endregion
#region inbound_hooks
// Requires NREHook.cs from https://github.com/Remod-org/NREHook
// We also check for the presence of this plugin to avoid callouts from other plugins.private void OnFoundNRE(string plugin, string unused)
{
Puts("BOGUS");
if (NREHook && plugin == Name && configData.Options.enableDebugOnErrors && !onfoundnre) {
if (configData.Options.autoDebugTime == 0) return;
Puts("Error encountered.ENABLING FULL DEBUG NOW!!!!!!!!!!");
// Enable full debug for defined time in seconds
configData.Options.debug = true;
dolog = true;
onfoundnre = true;
timer.Once(configData.Options.autoDebugTime, () => {
configData.Options.debug = false;
dolog = false;
onfoundnre = false;
Puts("AUTOMATED DEBUG NOW DISABLED..........");
}
);
}
}
private void ReloadConfig() => LoadConfigVariables(true);
private bool TogglePVE(bool on = true) => enabled = on;
private bool TogglePVERuleset(string rulesetname, bool on = true) {
string enable = "0";
if (on) enable = "1";
using SQLiteConnection c = new(connStr);
c.Open();
using SQLiteCommand cmd = new($"UPDATE ngpve_rulesets SET enabled='{enable}", c);
cmd.ExecuteNonQuery();
return true;
}
private bool IsInPVEZone(BaseCombatEntity target) {
string zone = "default";
if (ZoneManager && configData.Options.useZoneManager) {
string[] targetzone = GetEntityZones(target);
//DoLog($"Found {sourcezone.Length} source zone(s) and {targetzone.Length} target zone(s).");
if (targetzone.Length > 0) {
foreach (string z in targetzone) {
zone = z;
DoLog($"Found zone {zone}", 1);
break;
}
}
}
string mquery = "SELECT DISTINCT name, zone, damage, enabled FROM ngpve_rulesets WHERE zone='0' OR zone='default'";
switch (zone) {
case "default" : DoLog("Default ruleset query");
break;
default : mquery = $"SELECT DISTINCT name, zone, damage, enabled FROM ngpve_rulesets WHERE zone='{zone}' OR zone='lookup'";
break;
}
using SQLiteCommand findIt = new(mquery, sqlConnection);
using SQLiteDataReader readMe = findIt.ExecuteReader();
while (readMe.Read()) {
if (!readMe.IsDBNull(2) && !readMe.IsDBNull(3) && readMe.GetInt32(2) == 1 && readMe.GetInt32(3) == 0) {
// Default damage for this ruleset is ENabled but ruleset is DISabled.return true;
}
else if (!readMe.IsDBNull(2) && !readMe.IsDBNull(3) && readMe.GetInt32(2) == 0 && readMe.GetInt32(3) == 1) {
// Default damage for this ruleset is DISabled and ruleset is ENabled.return true;
}
}
return false;
}
private bool AddOrUpdateMapping(string zoneId, string rulesetname) {
if (string.IsNullOrEmpty(zoneId)) return false;
if (string.IsNullOrEmpty(rulesetname)) return false;
DoLog($"AddOrUpdateMapping called for ruleset: {rulesetname}, zone: {zoneId}", 0);
bool foundRuleset = false;
using (SQLiteConnection c = new(connStr)) {
c.Open();
using SQLiteCommand au = new($"SELECT DISTINCT enabled FROM ngpve_rulesets WHERE name='{rulesetname}'", c);
using SQLiteDataReader ar = au.ExecuteReader();
while (ar.Read()) {
foundRuleset = true;
}
}
if (foundRuleset) {
if (!ngpvezonemaps.ContainsKey(rulesetname)) {
ngpvezonemaps.Add(rulesetname, new NextGenPVEZoneMap());
}
if (!ngpvezonemaps[rulesetname].map.Contains(zoneId)) {
ngpvezonemaps[rulesetname].map.Add(zoneId);
}
// Verify zones are valid
ngpvezonemaps[rulesetname].map = CleanupZones(ngpvezonemaps[rulesetname].map);
SaveData();
using SQLiteConnection c = new(connStr);
c.Open();
DoLog($"UPDATE ngpve_rulesets SET zone='lookup' WHERE rulesetname='{rulesetname}'");
using SQLiteCommand cmd = new($"UPDATE ngpve_rulesets SET zone='lookup' WHERE name='{rulesetname}'", c);
cmd.ExecuteNonQuery();
return true;
}
else {
using (SQLiteConnection c = new(connStr)) {
c.Open();
DoLog($"INSERT INTO ngpve_rulesets VALUES('{rulesetname}', '1', '1', '1', 'lookup', '', '', '', '', 0, 0)");
using SQLiteCommand cmd = new($"INSERT INTO ngpve_rulesets VALUES('{rulesetname}', '1', '1', '1', 'lookup', '', '', '', '', 0, 0)", c);
cmd.ExecuteNonQuery();
}
if (ngpvezonemaps.ContainsKey(rulesetname)) ngpvezonemaps.Remove(rulesetname);
ngpvezonemaps.Add(rulesetname, new NextGenPVEZoneMap() {
map = new List<string>() {
zoneId
}
}
);
//Verify zones are valid
//ngpvezonemaps[rulesetname].map = CleanupZones(ngpvezonemaps[rulesetname].map);
SaveData();
return true;
}
}
private bool RemoveMapping(string zoneId) {
DoLog($"RemoveMapping called for zone: {zoneId}", 0);
bool found = false;
foreach (KeyValuePair<string, NextGenPVEZoneMap> map in ngpvezonemaps) {
if (map.Value.map is null) continue;
if (map.Value.map.Contains(zoneId)) {
ngpvezonemaps[map.Key].map.Remove(zoneId);
SaveData();
DoLog($"RemoveMapping found and removed {zoneId} from ruleset {map.Key}", 0);
found = true;
}
}
if (!found) {
DoLog($"RemoveMapping did not find {zoneId} in any ruleset", 0);
}
return found;
}
private object OnEntityTakeDamage(BaseCombatEntity entity, HitInfo hitinfo) {
if (!enabled) return null;
if (entity is null) return null;
if (hitinfo is null) return null;
Rust.DamageType majority = Rust.DamageType.Generic;
try {
majority = hitinfo.damageTypes.GetMajorityDamageType();
if (majority == Rust.DamageType.Decay || majority == Rust.DamageType.Radiation) return null;
}
catch {
}
if (configData.Options.debug) Puts("ENTRY:");
if (configData.Options.debug) Puts("Checking for null fall damage");
if (majority == Rust.DamageType.Fall && hitinfo?.Initiator is null) {
DoLog($"Null initiator for attack on {entity?.ShortPrefabName} by Fall");
if (BlockFallDamage(entity)) {
if (configData.Options.debug) Puts(":EXIT");
return true;
}
}
if (configData.Options.debug) Puts("Calling external damage hook");
try {
object CanTakeDamage = Interface.CallHook("CanEntityTakeDamage", new object[] {
entity, hitinfo
}
);
if (CanTakeDamage is object && CanTakeDamage is bool && (bool)CanTakeDamage) {
if (configData.Options.debug) Puts($"Calling external damage hook for {hitinfo?.Initiator} attacking {entity?.ShortPrefabName} PASSED: ALLOW\n:EXIT");
return null;
}
}
catch {
if (configData.Options.debug) Puts("Calling external damage hook FAILED!");
}
string stype = "";
string ttype = "";
bool canhurt;
DoLog("\nBEGIN:");
if (hitinfo?.WeaponPrefab is object && hitinfo?.WeaponPrefab?.ShortPrefabName == "rocket_mlrs") {
if (configData.Options.debug) Puts($"attacker prefab: {hitinfo?.WeaponPrefab?.ShortPrefabName}, victim prefab: {entity?.ShortPrefabName}");
if (configData.Options.debug) Puts("Calling EvaluateRulesets: MLRS");
canhurt = EvaluateRulesets(hitinfo?.WeaponPrefab, entity, hitinfo?.WeaponPrefab?.ShortPrefabName, out stype, out ttype);
}
//else if (!ReferenceEquals(hitinfo?.WeaponPrefab, null) && hitinfo?.WeaponPrefab?.ShortPrefabName == "homingmissile")
//{
// if (configData.Options.debug) Puts($"attacker prefab: {hitinfo?.WeaponPrefab?.ShortPrefabName}, victim prefab: {entity?.ShortPrefabName}");
// if (configData.Options.debug) Puts("Calling EvaluateRulesets: Missile Launcher");
// canhurt = EvaluateRulesets(hitinfo?.WeaponPrefab, entity, hitinfo?.WeaponPrefab?.ShortPrefabName, out stype, out ttype);
//}
else if (hitinfo?.Initiator is null) {
if (configData.Options.debug) Puts($"attacker prefab: {hitinfo?.Initiator?.ShortPrefabName}, victim prefab: {entity?.ShortPrefabName}");
if (configData.Options.debug) Puts("NOT calling EvaluateRulesets: NULL INITIATOR");
canhurt = true;
}
else {
if (configData.Options.debug) Puts($"attacker prefab: {hitinfo?.Initiator?.ShortPrefabName}, victim prefab: {entity?.ShortPrefabName}");
if (configData.Options.debug) Puts("Calling EvaluateRulesets: Standard");
canhurt = EvaluateRulesets(hitinfo?.Initiator, entity, hitinfo?.Initiator?.ShortPrefabName, out stype, out ttype);
}
if (stype.Length == 0 && ttype.Length == 0) {
if (configData.Options.debug) Puts("Calling EvaluateRulesets DETECTION FAILED");
return null;
}
if (configData.Options.debug) Puts("Calling EvaluateRulesets PASSED");
if (stype == "BasePlayer") {
BasePlayer hibp = hitinfo?.Initiator as BasePlayer;
if (configData.Options.debug) Puts("Checking for god perms");
//if (hibp != null && permission.UserHasPermission(hibp.UserIDString, permNextGenPVEGod))
if (hibp is object && permission.UserHasPermission(hibp?.UserIDString, permNextGenPVEGod)) {
Puts("Admin almighty!");
return null;
}
if (configData.Options.debug) Puts("Checking for suicide flag or friendly fire");
if (ttype == "BasePlayer") {
ulong sid = hibp.userID;
ulong tid = (entity as BasePlayer)?.userID ?? 0;
if (tid.IsSteamId() && sid > 0 && tid > 0) {
if (sid == tid && configData.Options.AllowSuicide) {
DoLog("AllowSuicide TRUE");
canhurt = true;
}
else {
object isfr = IsFriend(sid, tid);
if (isfr is object && isfr is bool && (bool)isfr && configData.Options.AllowFriendlyFire) {
DoLog("AllowFriendlyFire TRUE");
canhurt = true;
}
}
}
}
}
if (canhurt) {
DoLog($"DAMAGE ALLOWED for {stype} attacking {ttype}, majority damage type {majority}");
if (configData.Options.debug) Puts(":EXIT");
return null;
}
DoLog($"DAMAGE BLOCKED for {stype} attacking {ttype}, majority damage type {majority}");
if (configData.Options.debug) Puts(":EXIT");
return true;
}
#endregion
#region Main
private bool EvaluateRulesets(BaseEntity source, BaseEntity target, string srcPrefab, out string stype, out string ttype) {
stype = "";
ttype = "";
if (source is null || target is null) {
if (configData.Options.debug) Puts("Null source or target object");
return false;
}
if (configData.Options.debug) Puts("Getting source type");
if (srcPrefab == "rocket_mlrs") {
stype = "MLRS";
if (configData.Options.debug) Puts("Source is MLRS.");
}
else if (srcPrefab == "fireball_small_molotov") {
stype = "MolotovCocktail";
}
else if (IsPatrolHelicopter(source)) {
stype = "PatrolHelicopter";
if (configData.Options.debug) Puts("Source is helicopter.");
}
else {
stype = source?.GetType()?.Name;
}
if (configData.Options.debug) Puts("Getting target type");
ttype = target?.GetType()?.Name;
if (stype.Length == 0 || ttype.Length == 0) {
if (configData.Options.debug) Puts("Type detection failure.Bailing out.");
stype = "";
ttype = "";
return false;
}
if (configData.Options.debug) Puts($"attacker type: {stype}, victim type: {ttype}");
//if (source == target)
//{
// if (configData.Options.debug) Puts("Source and target are the same.");
// return true;
//}
// Special case for preventing codelock hacking
if (stype == "CodeLock" && ttype == "BasePlayer") {
DoLog("Allowing codelock damage");
return true;
}
string zone = "default";
bool hasBP = true;
bool ownsItem = true;
bool isBuilding = false;
//bool isHeli = false;
if (ttype == "BasePlayer") {
if (Humanoids && IsHumanoid(target)) ttype = "Humanoid";
if (HumanNPC && IsHumanNPC(target)) ttype = "HumanNPC";
// ... [Code preview truncated due to length. Full source in attached file] ...