C#:
// Все права принадлежат дискорд сообществу https://discord.gg/VgNHPpNrz6
using Facepunch;
using System;
using System.Linq;
using System.Reflection;
using System.Globalization;
using System.Collections.Generic;
using Oxide.Core;
using Oxide.Core.Plugins;
using Oxide.Game.Rust;
using UnityEngine;
using UnityEngine.AI;
using UnityEngine.SceneManagement;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using Rust.Ai.HTN;
namespace Oxide.Plugins {
[Info("BotSpawn", "Steenamaroo", "2.2.2", ResourceId = 15)]
[Description("Spawn tailored AI with kits at monuments, custom locations, or randomly.")]
class BotSpawn : RustPlugin {
[PluginReference]
Plugin Kits, CustomLoot, RustRewards;
int no_of_AI;
bool loaded;
Single currentTime;
static BotSpawn botSpawn;
const string permAllowed = "botspawn.allowed";
static System.Random random = new System.Random();
int GetRand(int l, int h) => random.Next(l, h);
public Dictionary<string, List<string>> KitList = new Dictionary<string, List<string>>();
public Dictionary<string, List<string>> NamesList = new Dictionary<string, List<string>>();
public Dictionary<string, PopInfo> popinfo = new Dictionary<string, PopInfo>();
public class PopInfo {
public int population;
public int queued;
}
public Dictionary<ulong, Timer> weaponCheck = new Dictionary<ulong, Timer>();
public static string Get(ulong v) => RandomUsernames.Get((int)(v % 2147483647uL));
bool HasPermission(string id, string perm) => permission.UserHasPermission(id, perm);
public Dictionary<string, List<Vector3>> spawnLists = new Dictionary<string, List<Vector3>>();
public bool IsNight => currentTime > configData.Global.NightStartHour || currentTime < configData.Global.DayStartHour;
Dictionary<ulong, string> editing = new Dictionary<ulong, string>();
public static Timer aridTimer, temperateTimer, tundraTimer, arcticTimer;
public Dictionary<string, Timer> timers = new Dictionary<string, Timer>() {
{
"BiomeArid", aridTimer
}
, {
"BiomeTemperate", temperateTimer
}
, {
"BiomeTundra", tundraTimer
}
, {
"BiomeArctic", arcticTimer
}
}
;
bool IsAuth(BasePlayer player) => player?.net?.connection?.authLevel == 2;
public Dictionary<ulong, NPCPlayerApex> NPCPlayers = new Dictionary<ulong, NPCPlayerApex>();
public Dictionary<string, DataProfile> AllProfiles = new Dictionary<string, DataProfile>();
void OnServerInitialized() {
timer.Once(1f, () => {
botSpawn = this;
currentTime = TOD_Sky.Instance.Cycle.Hour;
timer.Repeat(2f, 0, () => currentTime = TOD_Sky.Instance.Cycle.Hour);
CheckMonuments(false);
LoadConfigVariables();
storedData = Interface.Oxide.DataFileSystem.ReadObject<StoredData>($"BotSpawn/{configData.DataPrefix}-CustomProfiles");
defaultData = Interface.Oxide.DataFileSystem.ReadObject<DefaultData>($"BotSpawn/{configData.DataPrefix}-DefaultProfiles");
spawnsData = Interface.Oxide.DataFileSystem.ReadObject<SpawnsData>($"BotSpawn/{configData.DataPrefix}-SpawnsData");
var files = Interface.Oxide.DataFileSystem.GetFiles("BotSpawn");
StoredData storedUpdate;
UpdateData defaultUpdate;
string name;
foreach (var file in files) {
name = file.Substring(file.IndexOf("BotSpawn") + 9);
name = name.Substring(0, name.Length - 5);
if (file.Contains("-CustomProfiles")) {
storedUpdate = Interface.Oxide.DataFileSystem.ReadObject<StoredData>($"BotSpawn/{name}");
Interface.Oxide.DataFileSystem.WriteObject($"BotSpawn/{name}", storedUpdate);
}
if (file.Contains("-DefaultProfiles")) {
defaultUpdate = Interface.Oxide.DataFileSystem.ReadObject<UpdateData>($"BotSpawn/{name}");
Interface.Oxide.DataFileSystem.WriteObject($"BotSpawn/{name}", defaultUpdate);
}
}
SaveData();
SetupProfiles();
timer.Once(10, () => timer.Repeat(10, 0, () => AdjustPopulation()));
foreach (BasePlayer player in UnityEngine.Object.FindObjectsOfType<BasePlayer>()) {
foreach (var comp in player?.GetComponents<Component>()) if (comp?.GetType()?.Name == "HumanPlayer") {
HumanNPCs.Add(player.userID);
break;
}
}
loaded = true;
}
);
}
void Init() {
JsonConvert.DefaultSettings = () => new JsonSerializerSettings {
Formatting = Formatting.Indented, ReferenceLoopHandling = ReferenceLoopHandling.Ignore,
}
;
var filter = RustExtension.Filter.ToList();
filter.Add("cover points");
filter.Add("resulted in a conflict");
RustExtension.Filter = filter.ToArray();
no_of_AI = 0;
}
void Loaded() {
ConVar.AI.npc_families_no_hurt = false;
foreach (var entry in timers) spawnLists.Add(entry.Key, new List<Vector3>());
lang.RegisterMessages(Messages, this);
permission.RegisterPermission(permAllowed, this);
}
void Unload() {
foreach (var obj in RemoveObj.ToList()) UnityEngine.Object.DestroyImmediate(obj);
var filter = RustExtension.Filter.ToList();
filter.Remove("cover points");
filter.Remove("resulted in a conflict");
RustExtension.Filter = filter.ToArray();
Wipe();
}
void Wipe() {
foreach (var bot in NPCPlayers.ToDictionary(pair => pair.Key, pair => pair.Value).Where(bot => bot.Value != null)) NPCPlayers[bot.Key].Kill();
}
#region BiomeSpawnsSetup
void GenerateSpawnPoints(string name, int number, Timer myTimer, int biomeNo) {
int getBiomeAttempts = 0;
var spawnlist = spawnLists[name];
int halfish = Convert.ToInt16((ConVar.Server.worldsize / 2) / 1.1f);
var rand = UnityEngine.Random.insideUnitCircle * halfish;
if (AllProfiles[name].Kit.Count > 0 && Kits == null) {
PrintWarning(lang.GetMessage("nokits", this), name);
return;
}
timers[name] = timer.Repeat(0.01f, 0, () => {
bool finished = true;
if (spawnlist.Count < number + 10) {
getBiomeAttempts++;
if (getBiomeAttempts > 200 && spawnlist.Count == 0) {
PrintWarning(lang.GetMessage("noSpawn", this), name);
timers[name].Destroy();
return;
}
rand = UnityEngine.Random.insideUnitCircle * halfish;
Vector3 randomSpot = new Vector3(rand.x, 0, rand.y);
finished = false;
if (TerrainMeta.BiomeMap.GetBiome(randomSpot, biomeNo) > 0.5f) {
var point = CalculateGroundPos(new Vector3(randomSpot.x, 200, randomSpot.z));
if (point != Vector3.zero) spawnlist.Add(CalculateGroundPos(new Vector3(randomSpot.x, 200, randomSpot.z)));
;
}
}
if (finished) {
int i = 0;
var target = TargetAmount(AllProfiles[name]);
if (target > 0) {
int amount = CanRespawn(name, target, false);
if (amount > 0) {
timer.Repeat(2, amount, () => {
if (CanRespawn(name, 1, true) == 1) {
SpawnBots(name, AllProfiles[name], "biome", null, spawnlist[i], -1, true);
i++;
}
}
);
}
}
timers[name].Destroy();
}
}
);
}
public bool HasNav(Vector3 pos) {
NavMeshHit navMeshHit;
return NavMesh.SamplePosition(pos, out navMeshHit, 2, 1);
}
public static Vector3 CalculateGroundPos(Vector3 pos) {
pos.y = TerrainMeta.HeightMap.GetHeight(pos);
NavMeshHit navMeshHit;
if (!NavMesh.SamplePosition(pos, out navMeshHit, 2, 1)) pos = Vector3.zero;
else if (WaterLevel.GetWaterDepth(pos, true) > 0) pos = Vector3.zero;
else if (Physics.RaycastAll(navMeshHit.position + new Vector3(0, 100, 0), Vector3.down, 99f, 1235288065).Any()) pos = Vector3.zero;
else pos = navMeshHit.position;
return pos;
}
Vector3 TryGetSpawn(Vector3 pos, int radius) {
int attempts = 0;
var spawnPoint = Vector3.zero;
Vector2 rand;
while (attempts < 200 && spawnPoint == Vector3.zero) {
attempts++;
rand = UnityEngine.Random.insideUnitCircle * radius;
spawnPoint = CalculateGroundPos(pos + new Vector3(rand.x, 0, rand.y));
if (spawnPoint != Vector3.zero) return spawnPoint;
}
return spawnPoint;
}
int GetNextSpawn(string name, DataProfile profile) {
if (profile.UseCustomSpawns && spawnsData.CustomSpawnLocations[name].Count > 0) {
if (availableStatSpawns[name].Count == 0) return -1;
int spawnnum = availableStatSpawns[name][random.Next(availableStatSpawns[name].Count())];
availableStatSpawns[name].Remove(spawnnum);
return spawnnum;
}
return -1;
}
#endregion
#region population
void AdjustPop(string profile, int num) => popinfo[profile].population += num;
void AdjustQueue(string profile, int num) {
popinfo[profile].queued += num;
//catch, not a fix
if (popinfo[profile].queued < 0) popinfo[profile].queued = 0;
}
int TargetAmount(DataProfile profile) => IsNight ? profile.Night_Time_Spawn_Amount : profile.Day_Time_Spawn_Amount;
int CanRespawn(string name, int amount, bool second) {
int response = TargetAmount(AllProfiles[name]) - popinfo[name].population;
if (!second) response += popinfo[name].queued;
if (response > 0) {
if (!second) AdjustQueue(name, Mathf.Min(amount, response));
return Mathf.Min(amount, response);
}
else if (second) {
AdjustQueue(name, -1);
}
return 0;
}
Dictionary<string, List<int>> availableStatSpawns = new Dictionary<string, List<int>>();
void AdjustPopulation() {
foreach (var profile in AllProfiles.Where(x => x.Value.AutoSpawn == true && x.Key != "AirDrop" && x.Key != "HackableLockedCrate")) {
int targetAmount = TargetAmount(profile.Value);
var current = popinfo[profile.Key].population + popinfo[profile.Key].queued;
AdjustQueue(profile.Key, 0);
if (current < targetAmount) {
if (targetAmount == 0) continue;
popinfo[profile.Key].queued += targetAmount - current;
timer.Repeat(1f, targetAmount - current, () => {
if (profile.Value.UseCustomSpawns && spawnsData.CustomSpawnLocations.ContainsKey(profile.Key)) {
int spawnnum = 0, num = spawnsData.CustomSpawnLocations[profile.Key].Count;
if (num > 0) {
spawnnum = GetNextSpawn(profile.Key, profile.Value);
if (spawnnum == -1) {
Puts("ADJUSTPOP FAILING");
return;
}
SpawnBots(profile.Key, AllProfiles[profile.Key], null, null, new Vector3(), spawnnum, false);
if (profile.Value.Announce_Spawn && profile.Value.Announcement_Text != String.Empty) PrintToChat(profile.Value.Announcement_Text);
}
}
else {
if (timers.ContainsKey(profile.Key)) {
if (spawnLists[profile.Key].Count > 0) SpawnBots(profile.Key, AllProfiles[profile.Key], "biome", null, spawnLists[profile.Key][random.Next(spawnLists[profile.Key].Count)], -1, false);
}
else {
SpawnBots(profile.Key, AllProfiles[profile.Key], null, null, new Vector3(), -1, false);
if (profile.Value.Announce_Spawn && profile.Value.Announcement_Text != String.Empty) PrintToChat(profile.Value.Announcement_Text);
}
}
}
);
continue;
}
else {
foreach (var npc in NPCPlayers.ToList()) {
var bData = npc.Value.GetComponent<BotData>();
if (bData.monumentName == profile.Key && bData.respawn) {
if (popinfo[profile.Key].population > targetAmount) {
DontRespawn.Add(npc.Key);
npc.Value.Kill();
}
if (configData.Global.Staggered_Despawn) break;
}
}
}
}
}
#endregion
List<ulong> DontRespawn = new List<ulong>();
#region BotSetup
void DeployNpcs(Vector3 location, string name, DataProfile profile, string group, int num) => SpawnBots(name, profile, "Attack", group, location, num, true);
void SpawnBots(string name, DataProfile zone, string type, string group, Vector3 location, int spawnNum, bool initial) {
bool respawn = type != "Attack" && type != "AirDrop" && type != "HackableLockedCrate";
var pos = zone.Location;
var finalPoint = Vector3.zero;
bool stationary = zone.Stationary;
float rot = 0;
if (location == Vector3.zero && type != "AirDrop" && type != "HackableLockedCrate" && zone.UseCustomSpawns && spawnsData.CustomSpawnLocations[name].Count > 0) {
var customLoc = spawnNum == -1 ? spawnsData.CustomSpawnLocations[name][random.Next(spawnsData.CustomSpawnLocations[name].Count)] : spawnsData.CustomSpawnLocations[name][spawnNum];
Vector3 loc = s2v(customLoc);
rot = s2r(customLoc);
var l = mons.ContainsKey(zone.Parent_Monument) ? mons[zone.Parent_Monument].transform : mons.ContainsKey(name) ? mons[name].transform : null;
if (l != null) finalPoint = l.TransformPoint(loc);
if (!HasNav(finalPoint) && !zone.Stationary) {
PrintWarning(lang.GetMessage("noNav", this), name, spawnsData.CustomSpawnLocations[name].IndexOf(customLoc)+1);
return;
}
}
else stationary = false;
if (finalPoint == Vector3.zero) {
if (location != Vector3.zero) pos = location;
if (type != "biome") finalPoint = TryGetSpawn(pos, zone.Radius);
else finalPoint = location;
if (finalPoint == Vector3.zero) {
if (initial) Puts($"Can't get spawn point at {name}.Skipping one npc.");
AdjustQueue(name, -1);
return;
}
}
if (zone.Chute && !stationary) {
var rand = UnityEngine.Random.insideUnitCircle * zone.Radius;
finalPoint = type == "AirDrop" ? pos + new Vector3(rand.x, -40, rand.y) : new Vector3(finalPoint.x, 200, finalPoint.z);
}
NPCPlayer entity = (NPCPlayer)InstantiateSci(finalPoint, new Quaternion(), zone.Murderer);
var npc = entity.GetComponent<NPCPlayerApex>();
npc.Spawn();
NextTick(() => {
if (npc == null || npc.IsDestroyed || npc.IsDead()) {
if (respawn) AdjustQueue(name, -1);
if (spawnNum != -1 && respawn) availableStatSpawns[name].Add(spawnNum);
return;
}
if (!NPCPlayers.ContainsKey(npc.userID)) NPCPlayers.Add(npc.userID, npc);
else {
npc.Kill();
if (respawn) AdjustQueue(name, -1);
if (spawnNum != -1 && respawn) availableStatSpawns[name].Add(spawnNum);
return;
}
npc.EnablePlayerCollider();
timer.Once(1f, () => {
if (npc != null) {
var n = mons.ContainsKey(zone.Parent_Monument) ? mons[zone.Parent_Monument].transform : mons.ContainsKey(name) ? mons[name].transform : null;
if (n != null) {
npc.transform.rotation = Quaternion.Euler(0, n.transform.eulerAngles.y + rot, 0);
npc.SetFact(NPCPlayerApex.Facts.IsAggro, 0, false, false);
}
}
}
);
if (zone.Murderer) {
var suit = ItemManager.CreateByName("scarecrow.suit", 1, 0);
var eyes = ItemManager.CreateByName("gloweyes", 1, 0);
if (!suit.MoveToContainer(npc.inventory.containerWear)) suit.Remove();
if (!eyes.MoveToContainer(npc.inventory.containerWear)) eyes.Remove();
}
var bData = npc.gameObject.AddComponent<BotData>();
bData.stationary = stationary;
string kit = string.Empty;
string botname = string.Empty;
if (spawnNum != -1 && respawn) bData.CustomSpawnNum = spawnNum;
if (configData.Global.Kits_In_Order) {
if (KitList[name].Count == 0) KitList[name] = zone.Kit.ToList();
if (KitList[name].Count > 0) {
kit = KitList[name][0];
KitList[name].RemoveAt(0);
}
if (NamesList[name].Count == 0) NamesList[name] = zone.BotNames.ToList();
if (NamesList[name].Count > 0) {
botname = NamesList[name][0];
NamesList[name].RemoveAt(0);
}
}
else {
if (zone.Kit.Count > 0) kit = zone.Kit[random.Next(zone.Kit.Count)];
if (zone.BotNames.Count > 0) botname = zone.BotNames[random.Next(zone.BotNames.Count)];
}
bData.monumentName = name;
no_of_AI++;
bData.respawn = true;
bData.profile = zone.Clone();
bData.group = group ?? null;
bData.spawnPoint = finalPoint;
bData.biome = type == "biome";
npc.startHealth = zone.BotHealth;
npc.InitializeHealth(zone.BotHealth, zone.BotHealth);
npc.CommunicationRadius = 0;
npc.AiContext.Human.NextToolSwitchTime = Time.realtimeSinceStartup * 10;
npc.AiContext.Human.NextWeaponSwitchTime = Time.realtimeSinceStartup * 10;
if (zone.Chute && !stationary) AddChute(npc, finalPoint);
SetName(zone, npc, botname);
GiveKit(npc, zone, kit);
npc.clothingMoveSpeedReduction = -zone.Running_Speed_Boost;
SortWeapons(npc);
int suicInt = random.Next(zone.Suicide_Timer, zone.Suicide_Timer + 10);
if (!respawn) {
bData.respawn = false;
RunSuicide(npc, suicInt);
}
else {
AdjustPop(name, 1);
AdjustQueue(name, -1);
}
if (zone.Disable_Radio) npc.RadioEffect = new GameObjectRef();
ToggleAggro(npc, 0, zone.Aggro_Range);
npc.Stats.DeaggroChaseTime = 10;
npc.Stats.Defensiveness = 1;
}
);
}
BaseEntity InstantiateSci(Vector3 position, Quaternion rotation, bool murd) {
string type = murd ? "murderer" : "scientist";
string prefabname = $"assets/prefabs/npc/{type}/{type}.prefab";
GameObject gameObject = Instantiate.GameObject(GameManager.server.FindPrefab(prefabname), position, rotation);
gameObject.name = prefabname;
SceneManager.MoveGameObjectToScene(gameObject, Rust.Server.EntityScene);
if (gameObject.GetComponent<Spawnable>()) UnityEngine.Object.Destroy(gameObject.GetComponent<Spawnable>());
if (!gameObject.activeSelf) gameObject.SetActive(true);
BaseEntity component = gameObject.GetComponent<BaseEntity>();
return component;
}
void AddChute(NPCPlayerApex npc, Vector3 newPos) {
float wind = random.Next(0, Mathf.Min(100, configData.Global.Max_Chute_Wind_Speed)) / 40f;
float fall = random.Next(60, Mathf.Min(100, configData.Global.Max_Chute_Fall_Speed) + 60) / 20f;
var rb = npc.gameObject.GetComponent<Rigidbody>();
rb.isKinematic = false;
rb.useGravity = false;
rb.drag = 0f;
npc.gameObject.layer = 0;
//prevent_build layer fix
var fwd = npc.transform.forward;
rb.velocity = new Vector3(fwd.x * wind, 0, fwd.z * wind) - new Vector3(0, fall, 0);
var col = npc.gameObject.AddComponent<BoxCollider>();
col.size = new Vector3(1, 1f, 1);
//feet above ground
var Chute = GameManager.server.CreateEntity("assets/prefabs/misc/parachute/parachute.prefab", newPos, Quaternion.Euler(0, 0, 0));
Chute.gameObject.Identity();
Chute.SetParent(npc);
Chute.transform.localPosition += new Vector3(0, 1.3f, 0);
Chute.Spawn();
}
void SetName(DataProfile zone, NPCPlayerApex npc, string name) {
if (name == String.Empty) {
npc.displayName = Get(npc.userID);
npc.displayName = char.ToUpper(npc.displayName[0]) + npc.displayName.Substring(1);
}
else npc.displayName = name;
if (zone.BotNamePrefix != String.Empty) npc.displayName = zone.BotNamePrefix + " " + npc.displayName;
if (npc is Scientist) (npc as Scientist).LootPanelName = npc.displayName;
}
void GiveKit(NPCPlayerApex npc, DataProfile zone, string kit) {
if (npc == null || npc.inventory == null) return;
var bData = npc.GetComponent<BotData>();
string type = zone.Murderer ? "Murderer" : "Scientist";
if (kit != string.Empty) {
object checkKit = Kits?.CallHook("GetKitInfo", kit, true);
if (checkKit == null) {
PrintWarning($"Kit {kit} does not exist - Spawning default {type}.");
}
else {
bool weaponInBelt = false;
JObject kitContents = checkKit as JObject;
if (kitContents != null) {
JArray items = kitContents["items"] as JArray;
foreach (var weap in items) {
JObject item = weap as JObject;
if (item["container"].ToString() == "belt") weaponInBelt = true;
}
}
if (!weaponInBelt) {
PrintWarning($"Kit {kit} has no items in belt - Spawning default {type}.");
}
else {
if (bData.profile.Keep_Default_Loadout == false) npc.inventory.Strip();
Kits?.Call($"GiveKit", npc, kit, true);
}
}
}
}
void SortWeapons(NPCPlayerApex npc) {
if (npc == null) return;
var bData = npc.GetComponent<BotData>();
ItemDefinition fuel = ItemManager.FindItemDefinition("lowgradefuel");
foreach (var attire in npc.inventory.containerWear.itemList) if (attire.info.shortname.Equals("hat.miner") || attire.info.shortname.Equals("hat.candle")) {
bData.hasHeadLamp = true;
Item newItem = ItemManager.Create(fuel, 1);
attire.contents.Clear();
if (!newItem.MoveToContainer(attire.contents)) newItem.Remove();
else {
npc.SendNetworkUpdateImmediate();
npc.inventory.ServerUpdate(0f);
}
}
foreach (Item item in npc.inventory.containerBelt.itemList) {
var held = item.GetHeldEntity();
if (held != null && held as HeldEntity != null) {
if (held is FlameThrower || held.name.Contains("launcher")) continue;
if (held as BaseMelee != null || held as TorchWeapon != null) {
bData.Weapons[1].Add(item);
bData.Weapons[0].Add(item);
}
else if (held as BaseProjectile != null) {
bData.Weapons[0].Add(item);
if (held.name.Contains("m92") || held.name.Contains("pistol") || held.name.Contains("python") || held.name.Contains("waterpipe")) bData.Weapons[2].Add(item);
else if (held.name.Contains("bolt") || held.name.Contains("l96")) bData.Weapons[4].Add(item);
else bData.Weapons[3].Add(item);
}
}
}
if ((npc is Scientist && (bData.Weapons[0].Count == 0 || (bData.Weapons[0].Count == bData.Weapons[1].Count))) || npc is NPCMurderer && bData.Weapons[0].Count == 0) {
PrintWarning(lang.GetMessage("noWeapon", this), bData.monumentName);
bData.noweapon = true;
return;
}
npc.CancelInvoke(npc.EquipTest);
}
void RunSuicide(NPCPlayerApex npc, int suicInt) {
if (!NPCPlayers.ContainsKey(npc.userID)) return;
timer.Once(suicInt, () => {
if (npc == null) return;
if (npc.AttackTarget != null && Vector3.Distance(npc.transform.position, npc.AttackTarget.transform.position) < 10 && npc.GetNavAgent.isOnNavMesh) {
var position = npc.AttackTarget.transform.position;
npc.svActiveItemID = 0;
npc.SendNetworkUpdate(BasePlayer.NetworkQueue.Update);
npc.inventory.UpdatedVisibleHolsteredItems();
timer.Repeat(0.05f, 100, () => {
if (npc == null) return;
npc.SetDestination(position);
}
);
}
timer.Once(4, () => {
if (npc == null) return;
if (configData.Global.Suicide_Boom) Effect.server.Run("assets/prefabs/weapons/rocketlauncher/effects/rocket_explosion.prefab", npc.transform.position);
HitInfo nullHit = new HitInfo();
nullHit.damageTypes.Add(Rust.DamageType.Suicide, 10000);
npc.IsInvinsible = false;
npc.Die(nullHit);
}
);
}
);
}
#endregion
static BasePlayer FindPlayerByName(string name) {
BasePlayer result = null;
foreach (BasePlayer current in BasePlayer.activePlayerList) {
if (current.displayName.Equals(name, StringComparison.OrdinalIgnoreCase) || current.UserIDString.Contains(name, CompareOptions.OrdinalIgnoreCase) || current.displayName.Contains(name, CompareOptions.OrdinalIgnoreCase)) result = current;
}
return result;
}
#region Hooks
object OnNpcKits(ulong userID) {
return NPCPlayers.ContainsKey(userID) ? true : (object)null;
}
private object CanBeTargeted(BaseCombatEntity player, BaseEntity entity) {
NPCPlayer npcPlayer = player as NPCPlayer;
return (npcPlayer != null && NPCPlayers.ContainsKey(npcPlayer.userID) && configData.Global.Turret_Safe) ? false : (object)null;
}
private object CanBradleyApcTarget(BradleyAPC bradley, BaseEntity target) {
NPCPlayer npcPlayer = target as NPCPlayer;
return (npcPlayer != null && NPCPlayers.ContainsKey(npcPlayer.userID) && configData.Global.APC_Safe) ? false : (object)null;
}
object OnEntityTakeDamage(BaseCombatEntity entity, HitInfo info) {
var botMelee = info?.Initiator as BaseMelee;
bool melee = false;
if (botMelee != null) {
melee = true;
info.Initiator = botMelee.GetOwnerPlayer();
}
NPCPlayerApex bot = entity as NPCPlayerApex;
BotData bData;
//If victim is one of mine
if (bot != null && NPCPlayers.ContainsKey(bot.userID)) {
if (configData.Global.APC_Safe && info?.Initiator is BradleyAPC) return true;
var attackPlayer = info?.Initiator as BasePlayer;
bData = bot.GetComponent<BotData>();
if (configData.Global.Pve_Safe) {
if (info.Initiator?.ToString() == null && info.damageTypes.GetMajorityDamageType() == Rust.DamageType.Bullet) return true;
//new autoturrets
if (info.Initiator?.ToString() == null || info.Initiator.ToString().Contains("cactus") || info.Initiator.ToString().Contains("barricade")) {
info.damageTypes.ScaleAll(0);
return true;
}
}
if (attackPlayer != null) {
if (NPCPlayers.ContainsKey(attackPlayer.userID)) //and attacker is one of mine
if (attackPlayer.GetComponent<BotData>().monumentName == bData.monumentName) return true;
//wont attack their own
if (configData.Global.PeaceKeepers_Ignore_Melee) {
if (bData.profile.Peace_Keeper && (info.Weapon is BaseMelee || info.Weapon is TorchWeapon)) //prevent melee farming with peacekeeper on
{
info.damageTypes.ScaleAll(0);
return true;
}
}
if (info != null && bData.profile.Die_Instantly_From_Headshot && info.isHeadshot) {
var weap = info?.Weapon?.ShortPrefabName;
var weaps = bData.profile.Instant_Death_From_Headshot_Allowed_Weapons;
if (weaps.Count == 0 || (weap != null && weaps.Contains(weap))) {
info.damageTypes.Set(0, bot.health);
return null;
}
}
if (Vector3.Distance(attackPlayer.transform.position, bot.transform.position) > bot.Stats.AggressionRange) {
if (bot.Stats.AggressionRange < 400) {
bot.Stats.AggressionRange += 400;
bot.Stats.DeaggroRange += 400;
}
ForceMemory(bot, attackPlayer);
timer.Repeat(1f, 20, () => {
if (bot != null) {
bot.RandomMove();
if (bot.AttackTarget != null && bot.AttackTarget.IsVisible(bot.eyes.position, (bot.AttackTarget as BasePlayer).eyes.position, 400)) Rust.Ai.HumanAttackOperator.AttackEnemy(bot.AiContext, Rust.Ai.AttackOperator.AttackType.LongRange);
}
}
);
timer.Once(20, () => {
if (bot == null) return;
ToggleAggro(bot, 0, bData.profile.Aggro_Range);
}
);
}
bot.AttackTarget = attackPlayer;
bot.lastAttacker = attackPlayer;
bData.goingHome = false;
}
}
NPCPlayerApex attackNPC = info?.Initiator as NPCPlayerApex;
//if attacker is one of mine
if (attackNPC != null && entity is BasePlayer && NPCPlayers.ContainsKey(attackNPC.userID)) {
bData = attackNPC.GetComponent<BotData>();
float rand = GetRand(1, 101);
float distance = Vector3.Distance(info.Initiator.transform.position, entity.transform.position);
float newAccuracy = bData.profile.Bot_Accuracy_Percent;
float newDamage = bData.profile.Bot_Damage_Percent / 100f;
if (distance > 100f) {
newAccuracy = bData.profile.Bot_Accuracy_Percent / (distance / 100f);
newDamage = newDamage / (distance / 100f);
}
if (!melee && newAccuracy < rand) return true;
info.damageTypes.ScaleAll(newDamage);
}
return null;
}
void OnPlayerDeath(BasePlayer player, HitInfo info) {
if (player == null) return;
if (HumanNPCs.Contains(player.userID)) HumanNPCs.Remove(player.userID);
NPCPlayerApex npc = player as NPCPlayerApex;
if (npc != null) OnEntityKill(npc, info);
}
List<ulong> CrateIDs = new List<ulong>();
void OnEntityKill(NPCPlayerApex npc, HitInfo info) {
if (npc?.userID != null && NPCPlayers.ContainsKey(npc.userID) && !botInventories.ContainsKey(npc.userID)) {
var bData = npc.GetComponent<BotData>();
if (bData == null) return;
if (!AllProfiles.ContainsKey(bData.monumentName)) return;
//void OnBotSpawnNpcKilled(string ProfileName, Vector3 position, bool RespawningProfile, bool KilledByBotSpawn)
Interface.Oxide.CallHook("OnBotSpawnNpcKilled", bData.monumentName, npc.transform.position, bData.respawn, DontRespawn.Contains(npc.userID));
if (bData.respawn) AdjustPop(bData.monumentName, -1);
var pos = npc.transform.position;
if (info?.InitiatorPlayer != null) {
if (bData.profile.RustRewardsValue > 0) {
var weapon = info?.Weapon?.GetItem()?.info?.shortname ?? info?.WeaponPrefab?.ShortPrefabName;
RustRewards?.Call("GiveRustReward", info.InitiatorPlayer, 0, bData.profile.RustRewardsValue, npc, weapon, Vector3.Distance(info.InitiatorPlayer.transform.position, npc.transform.position), null);
}
if (bData.profile.Spawn_Hackable_Death_Crate_Percent > GetRand(1, 101) && npc.WaterFactor() < 0.1f) {
timer.Once(2f, () => {
var Crate = GameManager.server.CreateEntity("assets/prefabs/deployable/chinooklockedcrate/codelockedhackablecrate.prefab", pos + new Vector3(1, 2, 0), Quaternion.Euler(0, 0, 0));
Crate.Spawn();
CrateIDs.Add(Crate.net.ID);
(Crate as HackableLockedCrate).hackSeconds = HackableLockedCrate.requiredHackSeconds - bData.profile.Death_Crate_LockDuration;
timer.Once(1.4f, () => {
if (Crate == null) return;
if (CustomLoot && bData.profile.Death_Crate_CustomLoot_Profile != string.Empty) {
var container = Crate?.GetComponent<StorageContainer>();
if (container != null) {
container.inventory.capacity = 36;
container.onlyAcceptCategory = ItemCategory.All;
container.SendNetworkUpdateImmediate();
container.inventory.Clear();
List<Item> loot = (List<Item>)CustomLoot?.Call("MakeLoot", bData.profile.Death_Crate_CustomLoot_Profile);
if (loot != null) foreach (var item in loot) if (!item.MoveToContainer(container.inventory, -1, true)) item.Remove();
}
}
}
);
}
);
}
}
Item activeItem = npc.GetActiveItem();
if (bData.profile.Weapon_Drop_Percent >= GetRand(1, 101) && activeItem != null) {
var numb = GetRand(Mathf.Min(bData.profile.Min_Weapon_Drop_Condition_Percent, bData.profile.Max_Weapon_Drop_Condition_Percent), bData.profile.Max_Weapon_Drop_Condition_Percent);
numb = Convert.ToInt16((numb / 100f) * activeItem.maxCondition);
activeItem.condition = numb;
activeItem.Drop(npc.eyes.position, new Vector3(), new Quaternion());
npc.svActiveItemID = 0;
npc.SendNetworkUpdate(BasePlayer.NetworkQueue.Update);
}
ItemContainer[] source = {
npc.inventory.containerMain, npc.inventory.containerWear, npc.inventory.containerBelt
}
;
Inv botInv = new Inv() {
profile = bData.profile
}
;
botInventories.Add(npc.userID, botInv);
for (int i = 0; i < source.Length; i++) foreach (var item in source[i].itemList) botInv.inventory[i].Add(new InvContents {
ID = item.info.itemid, amount = item.amount, skinID = item.skin,
}
);
if (bData.profile.Disable_Radio == true) npc.DeathEffect = new GameObjectRef();
DeadNPCPlayerIds.Add(npc.userID);
no_of_AI--;
if (bData.respawn == false) return;
if (bData.biome && spawnLists.ContainsKey(bData.monumentName)) {
List<Vector3> spawnList = new List<Vector3>();
spawnList = spawnLists[bData.monumentName];
int spawnPos = random.Next(spawnList.Count);
if (CanRespawn(bData.monumentName, 1, false) == 1) {
timer.Once(bData.profile.Respawn_Timer, () => {
if (AllProfiles.ContainsKey(bData.monumentName)) {
if (CanRespawn(bData.monumentName, 1, true) == 1) {
SpawnBots(bData.monumentName, AllProfiles[bData.monumentName], "biome", null, spawnList[spawnPos], -1, false);
if (bData.profile.Announce_Spawn && bData.profile.Announcement_Text != String.Empty) PrintToChat(bData.profile.Announcement_Text);
}
}
}
);
}
return;
}
int num = spawnsData.CustomSpawnLocations[bData.monumentName].Count;
if (!DontRespawn.Contains(npc.userID) && CanRespawn(bData.monumentName, 1, false) == 1) {
timer.Once(bData.profile.Respawn_Timer, () => {
int spawnnum = 0;
if (AllProfiles.ContainsKey(bData.monumentName)) {
if (CanRespawn(bData.monumentName, 1, true) == 1) {
if (num > 0 && bData.profile.UseCustomSpawns) {
if (!bData.profile.ChangeCustomSpawnOnDeath) spawnnum = bData.CustomSpawnNum;
else {
availableStatSpawns[bData.monumentName].Add(bData.CustomSpawnNum);
spawnnum = GetNextSpawn(bData.monumentName, bData.profile);
}
}
SpawnBots(bData.monumentName, AllProfiles[bData.monumentName], null, null, new Vector3(), spawnnum, false);
if (bData.profile.Announce_Spawn && bData.profile.Announcement_Text != String.Empty) PrintToChat(bData.profile.Announcement_Text);
}
else if (bData.profile.UseCustomSpawns) availableStatSpawns[bData.monumentName].Add(bData.CustomSpawnNum);
}
}
);
}
else {
if (DontRespawn.Contains(npc.userID)) DontRespawn.Remove(npc.userID);
if (bData.profile.UseCustomSpawns) availableStatSpawns[bData.monumentName].Add(bData.CustomSpawnNum);
}
}
}
public static readonly FieldInfo AllScientists = typeof(Scientist).GetField("AllScientists", (BindingFlags.Static | BindingFlags.Public));
//NRE AskQuestion workaround
void OnEntitySpawned(Scientist sci) {
if (loaded && sci != null) AllScientists.SetValue(sci, new HashSet<Scientist>());
//NRE AskQuestion workaround
}
void OnEntitySpawned(DroppedItemContainer container) {
NextTick(() => {
if (!loaded || container == null || container.IsDestroyed) return;
if (container.playerSteamID == 0) return;
if (configData.Global.Remove_BackPacks_Percent >= GetRand(1, 101)) if (DeadNPCPlayerIds.Contains(container.playerSteamID)) {
container.Kill();
DeadNPCPlayerIds.Remove(container.playerSteamID);
return;
}
}
);
}
void OnEntitySpawned(SupplySignal signal) {
timer.Once(2.3f, () => {
if (!loaded || signal != null) SmokeGrenades.Add(new Vector3(signal.transform.position.x, 0, signal.transform.position.z));
}
);
}
void OnEntitySpawned(SupplyDrop drop) {
if (!loaded || (!drop.name.Contains("supply_drop") && !drop.name.Contains("sleigh/presentdrop"))) return;
if (!configData.Global.Supply_Enabled) {
foreach (var location in SmokeGrenades.Where(location => Vector3.Distance(location, new Vector3(drop.transform.position.x, 0, drop.transform.position.z)) < 35f)) {
SmokeGrenades.Remove(location);
return;
}
}
if (AllProfiles.ContainsKey("AirDrop")) {
var prof = AllProfiles["AirDrop"];
if (prof.AutoSpawn == true && prof.Day_Time_Spawn_Amount > 0) {
var profile = AllProfiles["AirDrop"];
if (profile.Announce_Spawn && profile.Announcement_Text != String.Empty) PrintToChat(profile.Announcement_Text);
timer.Repeat(1f, profile.Day_Time_Spawn_Amount, () => {
if (drop == null) return;
profile.Location = drop.transform.position;
SpawnBots("AirDrop", profile, "AirDrop", null, new Vector3(), -1, true);
}
);
}
}
}
void OnCrateHack(HackableLockedCrate crate) {
if (!HasNav(crate.transform.position)) return;
NextTick(() => {
if (crate == null || CrateIDs.Contains(crate.net.ID)) return;
if (CrateIDs.Contains(crate.net.ID)) CrateIDs.Remove(crate.net.ID);
if (AllProfiles.ContainsKey("HackableLockedCrate")) {
var prof = AllProfiles["HackableLockedCrate"];
if (prof.AutoSpawn == true && prof.Day_Time_Spawn_Amount > 0) {
var profile = AllProfiles["HackableLockedCrate"];
if (profile.Announce_Spawn && profile.Announcement_Text != String.Empty) PrintToChat(profile.Announcement_Text);
timer.Repeat(1f, profile.Day_Time_Spawn_Amount, () => {
if (crate == null) return;
profile.Location = crate.transform.position;
SpawnBots("HackableLockedCrate", profile, "HackableLockedCrate", null, new Vector3(), -1, true);
}
);
}
}
}
);
}
void OnEntitySpawned(NPCPlayerCorpse corpse) {
if (!loaded || corpse == null) return;
ulong id = corpse.playerSteamID;
timer.Once(0.1f, () => {
if (corpse == null || corpse.IsDestroyed || !botInventories.ContainsKey(id)) return;
Inv botInv = botInventories[id];
DataProfile profile = botInv.profile;
timer.Once(profile.Corpse_Duration, () => {
if (corpse != null && !corpse.IsDestroyed) corpse?.Kill();
}
);
timer.Once(2, () => corpse?.ResetRemovalTime(profile.Corpse_Duration));
List<Item> toDestroy = new List<Item>();
foreach (var item in corpse.containers[0].itemList) {
if (item.ToString().ToLower().Contains("keycard") && configData.Global.Remove_KeyCard) toDestroy.Add(item);
}
foreach (var item in toDestroy) item.Remove();
if (!(profile.Allow_Rust_Loot_Percent >= GetRand(1,
// ... [Code preview truncated due to length. Full source in attached file] ...