using Facepunch; using Oxide.Core.Plugins; using ProtoBuf; using System.Collections.Generic; using System.Linq; using UnityEngine; using UnityEngine.AI; namespace Oxide.Plugins { [Info("RECustomBots", "bmgjet", "1.0.6")] [Description("Improves bots created with NPC_Spawner in rust edit")] public class RECustomBots : RustPlugin { public bool DebugInfo = false; //Plugin will remove any prefab at this exact location on server starting. You use it as a copy protection by placing something that stops //servers from booting like a invisable pumpjack item on your map. Then copy its position value into below. Vector3 MapLockLocation = new Vector3(0, 0, 0); //Fixes doors collider to a smaller size so player can walk closer to them. But not break collider allowing bots to shoot though. bool FixDoors = true; //Prefab placeholder string prefabplaceholder = "assets/prefabs/deployable/playerioents/gates/randswitch/electrical.random.switch.deployed.prefab"; //Bot Tick Rate float bot_tick = 1f; //Default Apex was 0.5f, Default AINew was 0.25f //Place holder scan distance float ScanDistance = 1f; //mount scan distance float MountScan = 8f; //Change the icon of the message announcer ulong AnnouncementIcon = 0; //Colour of announcer string color = "red"; //Protect Sleepers bool SleepProtect = true; //Parachute Suiside Timer (will cut parachute off after this many seconds) float ChuteSider = 20f; //Checks this area around parachute for collision float ColliderDistance = 2f; //List of random taunt dead messages to send {N} gets replaced with attacker/victims name. private string[] Dead = { "You got me, GG", ":(", "Thats not nice {N}", "Come on man?", "Did you have to do that {N}", "Argh again", "Ur 2 gud {N}", "SAD GUY", }; private string[] Killed = { "haha your bad {N}", ":P EZ", "Git Gud {N}", "Wasted", "Im not even trying {N}", "HAHAHAHA {N}", }; // * New Settings method using keywords in any order. // * If a keyword isnt provided it will use the default for that setting. // * Only what you provide is adjusted // * // * How to use: // * Seperate keywords by "." which will show as " " when in rust edit looking at prefab groups. // * Prefab group name must start with BMGBOT. // * // * Avaliable keywords // * name= if set it will apply a specific name to the bot, Other wise will pick one at random based on userid the spawner creates. // * kit= if set will apply kit to bot, Can do male/female specific kits by using ^ between them kit=malekit^femalekit // * stationary if this keyword is present bot will remain stationary. // * parachute if this keyword is present bot will parachute to navmesh. // * replace if this keyword is present bot replace default items with kit items. // * strip if this keyword is present bot will strip all its lot on death. // * radiooff if this keyword is present bot will not use the radio to chatter. // * peacekeeper if this keyword is present bot will only fire on hostile players. // * mount if this keyword is present bot will mount the closest seat. // * taunt if this keyword is present bot will make taunts in the chat to players it interacts with. // * killnotice if this keyword is present kills/deaths of this bot will be announced to chat. // * health= if set will adjust the bots health to this example health=150 // * attack= if set the boot will attack only up to this range loss of sight is a further 10f from this setting. // * roam= if set this is how far the bot can move from its home spawn before it wants to return to home. // * cooldown= if set changes the default home check rate. // * height= if set can make small adjustment to bots navmesh height usuall settings range will be -3 to +3. // * speed= if set adjusts how fast the bot can run. // * steamicon= if set the bot will use the steamicon from this steamid example would be steamicon=76561198188915047 // * apcattack if this keyword is present bot will be targeted by the APC // * canheal if this keyword is present bot will heal its self when its at half halth if its kitted with medical items. // * damageScale= What percentage of normal damage the bot will do. 100% damage is the default which is same damage a player does. // * accuracy= What percentage of shots will be at the target. 50% is the default. // * // * Example: bot with 500hp health and is stionary // * BMGBOT.stationary.health=500 // * // * Example: bot that has a 2 different kits for males and females, parachutes in, radio chatter disabled, default items removed. // * BMGBOT.kit=guy1^girl1.radiooff.parachute.replace // * // * Example: bot with custom name is a peacekeeper and sitting in a chair // * BMGBOT.name=Lazy Bot.peacekeeper.mount //Weapons with issues //crossbow.entity //bow_hunter.entity //speargun.entity //Layers of collision int parachuteLayer = 1 << (int)Rust.Layer.Water | 1 << (int)Rust.Layer.Transparent | 1 << (int)Rust.Layer.World | 1 << (int)Rust.Layer.Construction | 1 << (int)Rust.Layer.Debris | 1 << (int)Rust.Layer.Default | 1 << (int)Rust.Layer.Terrain | 1 << (int)Rust.Layer.Tree | 1 << (int)Rust.Layer.Vehicle_Large | 1 << (int)Rust.Layer.Deployed; //Stored location of spawner and its settings Dictionary NPC_Spawners = new Dictionary(); //Stored bot ID and what location to get settings from Dictionary NPC_Bots = new Dictionary(); //Store bots items Dictionary> NPC_Items = new Dictionary>(); //Ignored shots List IgnoredShots = new List(); //Store List of place holders to remove List PlaceHolders = new List(); //Check if fully started. private bool HasLoadedSpawner = false; //Random list of Steam IDs to use for bot images private ulong[] SteamIds = { 76561198201252634, 76561199110323558, 76561199194720194, 76561198129951438, 76561198144464080, 76561198101891231, 76561198157637478, 76561199225578578, 76561198188915047, 76561199219052430, 76561198138463675, 76561199172929853, 76561198401529908, 76561198032752525, 76561199135596964, 76561198065829079, 76561198800018143, 76561198091879314 }; //Last taunt said to prevent it repeating them in a row. int lasttaunt = -1; //reference to kits plugin [PluginReference] private Plugin Kits; //reference to self private static RECustomBots plugin; //Process Kit items into Botinfo Botsinfo ProcessKitItem(Item item) { Botsinfo bi = new Botsinfo(); bi.item_ammount = item.amount; bi.idef = item.info; bi.item_skin = item.skin; return bi; } //Info on items held by bot. private class Botsinfo { public ItemDefinition idef; public int item_ammount; public ulong item_skin; } //Settings of the bot. private class BotsSettings { //BotSettings Defaults public string kitname = "bmgjet"; public bool stationary = false; public int health = 0; public float AttackRange = 30f; public int roamrange = 20; public int cooldown = 30; public bool replaceitems = false; public bool parachute = false; public string name = ""; public bool radiooff = false; public bool peacekeeper = false; public bool taunts = false; public bool killnotice = false; public bool apcattack = false; public bool canheal = false; public float height = 0f; public ulong steamicon = 0; public bool strip = false; public bool mount = false; public int speed = -1; public int damageScale = 100; public int accuracy = 30; } //Bot script public class BMGBOT : MonoBehaviour { //vars int LastInteraction = 0; int FailedHomes = 0; bool flying = true; Vector3 Home; BotsSettings settings; //References BasePlayer bp; HumanNPC bot; NPCPlayer NPC; ScarecrowBrain SBrain; BaseNavigator BN; //is healing bool Healing = false; //Held Gun Item Gun; //Keep track of parachute collider for other plugins CapsuleCollider paracol; private void Awake() { bp = this.GetComponent(); bot = this.GetComponent(); BN = this.GetComponent(); NPC = this.GetComponent(); if (bot == null) { SBrain = this.GetComponent().Brain; } if (bp == null || BN == null || NPC == null) return; //Check if its a bot from spawner if (plugin.NPC_Bots.ContainsKey(bp.userID)) { //Set a home point Home = plugin.NPC_Bots[bp.userID]; var col = bp.gameObject.AddComponent(); col.size = new Vector3(1, 1f, 1); //Load settings settings = plugin.NPC_Spawners[plugin.NPC_Bots[bp.userID]]; //Check there are settings if (settings == null) { //No Settings so be default return; } //Pick a random Icon if (settings.steamicon == 0) { settings.steamicon = plugin.SteamIds[Random.Range(0, plugin.SteamIds.Length - 1)] + (uint)100; } //Set as own owner bp.OwnerID = bp.userID; //Set bots name if (settings.name == "" || settings.name == "0") { //Face Punches random function using steamid settings.name = RandomUsernames.Get((int)bp.userID); bp.name = settings.name; plugin.NPC_Spawners[plugin.NPC_Bots[bp.userID]].name = settings.name; } else { //Set custom botname bp.name = settings.name; } //Update the bot bp.displayName = settings.name; //Set bot health if (settings.health != 0) { bp.startHealth = settings.health; bp.InitializeHealth(settings.health, settings.health); } //Stop bot moving until its activated if (settings.stationary) { BN.CanUseNavMesh = false; } //Stops attack range being completely 0 or negitive if (settings.AttackRange <= 0) { settings.AttackRange = 1.5f; } if (settings.roamrange <= 0) { settings.roamrange = 5; } //Adds parachute to spawning if (settings.parachute) { //Trigger flying and get collider around NPC flying = true; paracol = bp.GetComponent(); if (paracol != null) { //If not created then adjust radius paracol.isTrigger = true; bp.GetComponent().radius += 4f; } //Move bot bp.transform.position = Home + new Vector3(0, 100, 0); bp.gameObject.layer = 0; //Adjust phyics stuff var rb = bp.gameObject.GetComponent(); if (rb != null) { rb.drag = 0f; rb.useGravity = false; rb.isKinematic = false; rb.velocity = new Vector3(bp.transform.forward.x * 0, 0, bp.transform.forward.z * 0) - new Vector3(0, 10, 0); } //Create the parachute var Chute = GameManager.server.CreateEntity("assets/prefabs/misc/parachute/parachute.prefab", bp.transform.position, Quaternion.Euler(0, 0, 0)); if (Chute != null) { Chute.gameObject.Identity(); //Offset it to players back Chute.transform.localPosition = Chute.transform.localPosition + new Vector3(0f, 1.3f, 0f); //Attach player and spawn parachute Chute.SetParent(bp); Chute.Spawn(); plugin.ParachuteSuiside(bp); } } else { //Attach bot to the ground. ClipGround(); } //Sets kit onto bot if ones set if (settings.kitname != "") { string kitname = settings.kitname; if (settings.kitname.Contains("^")) { //Do male or female specific kits if (IsFemale(NPC.userID)) { kitname = kitname.Split('^')[1]; } else { kitname = kitname.Split('^')[0]; } } if (plugin.IsKit(kitname)) { plugin.BotSkin(NPC, kitname, settings.replaceitems); } } //stops down spamming when cool down set too low if (settings.cooldown < 5) { settings.cooldown = 10; } //Do setting up of behavour if (bot != null) { bot.Brain.SenseRange = settings.AttackRange; bot.Brain.ListenRange = settings.AttackRange; bot.Brain.TargetLostRange = settings.AttackRange; bot.Brain.Navigator.MaxRoamDistanceFromHome = settings.roamrange; bot.Brain.AttackRangeMultiplier = 1f; bot.Brain.Senses.Memory.Targets.Clear(); bot.Brain.IgnoreSafeZonePlayers = true; //Adjust speed if (settings.speed != -1) { bot.Brain.Navigator.Speed = settings.speed; } //peacekeeper check; bot.Brain.HostileTargetsOnly = settings.peacekeeper; bot.Brain.Senses.Update(); } else if (SBrain != null) { SBrain.SenseRange = settings.AttackRange; SBrain.ListenRange = settings.AttackRange; SBrain.TargetLostRange = settings.AttackRange; SBrain.Navigator.MaxRoamDistanceFromHome = settings.roamrange; SBrain.AttackRangeMultiplier = 1f; SBrain.Senses.Memory.Targets.Clear(); SBrain.IgnoreSafeZonePlayers = true; //Adjust speed if (settings.speed != -1) { SBrain.Navigator.Speed = settings.speed; } //peacekeeper check; SBrain.HostileTargetsOnly = settings.peacekeeper; SBrain.Senses.Update(); } else { bp.Kill(); return; } //Allows bot every topo BN.topologyPreference = ((TerrainTopology.Enum)TerrainTopology.EVERYTHING); NPC.damageScale = settings.damageScale / 100f; //Output Debug Info if (plugin.DebugInfo) plugin.Puts("Bot " + bp.displayName + " spawned,Health:" + bp.health + " Kit:" + settings.kitname + " Range:" + settings.AttackRange.ToString() + " Roam:" + settings.roamrange.ToString() + " Cooldown:" + settings.cooldown.ToString() + " Default Items:" + !settings.replaceitems + " Stationary:" + settings.stationary.ToString() + " Parachute:" + settings.parachute); //Update Server With Bot bp.SendNetworkUpdate(); //Setup repeating script after 5 secs at tick rate InvokeRepeating("_tick", 5, plugin.bot_tick); } } public BasePlayer GetBestTarget() { //Find best target from the list of targets the bot has List Targets = new List(); //Get settings from senses float SenseRange = settings.AttackRange; float VisionCone = 0; if (bot != null) { Targets = bot.Brain.Senses.Memory.Targets; SenseRange = bot.Brain.SenseRange; VisionCone = bot.Brain.VisionCone; } else if (SBrain != null) { Targets = SBrain.Senses.Memory.Targets; SenseRange = SBrain.SenseRange; VisionCone = SBrain.VisionCone; } else { return null; } BasePlayer target = null; float delta = -1f; foreach (BaseEntity baseEntity in Targets) { //Dont target low health or dead if (baseEntity == null || baseEntity.Health() <= 0f) continue; BasePlayer basePlayer = baseEntity as BasePlayer; if (!CanTargetPlayer(basePlayer)) continue; //Get closest player float rangeDelta = 1f - Mathf.InverseLerp(1f, SenseRange, Vector3.Distance(basePlayer.transform.position, bp.transform.position)); float dot = Vector3.Dot((basePlayer.transform.position - bp.eyes.position).normalized, bp.eyes.BodyForward()); //Check if in vision if (dot < VisionCone) continue; rangeDelta += Mathf.InverseLerp(VisionCone, 1f, dot) / 2f; if (bot != null) { rangeDelta += (bot.Brain.Senses.Memory.IsLOS(basePlayer) ? 2f : 0f); } else { rangeDelta += (SBrain.Senses.Memory.IsLOS(basePlayer) ? 2f : 0f); } if (rangeDelta <= delta) continue; target = basePlayer; delta = rangeDelta; } if (plugin.DebugInfo && target != null) { plugin.Puts("Found Target " + target.ToString()); } return target; } public bool CanTargetPlayer(BasePlayer player) { //Conditions not to attack under if (player == null || player.IsFlying || player.IsSleeping() || player.IsWounded() || player.IsDead()) return false; return true; } private void ClipGround() { //get rigidbody reference var rb = bp.gameObject.GetComponent(); //Scan for ground. NavMeshHit hit; if (NavMesh.SamplePosition(bp.transform.position, out hit, 30, -1)) { //parachute colider reference remove if (paracol != null) { paracol.isTrigger = false; bp.GetComponent().radius -= 4f; } //Water check if (bp.WaterFactor() > 0.9f) { bp.Kill(); return; } //Remove any phyics alterations rb.isKinematic = true; rb.useGravity = false; bp.gameObject.layer = 17; //Offset adjustment bp.ServerPosition = hit.position -= new Vector3(0, (settings.height / 10), 0); BN.Agent.Move(bp.ServerPosition); //Remove any attached Parachutes bool Destroyed = false; foreach (var child in bp.children.Where(child => child.name.Contains("parachute"))) { child.SetParent(null); child.Kill(); Destroyed = true; break; } //Play sound fx if a parachute gets destroyed if (Destroyed) { Effect.server.Run("assets/bundled/prefabs/fx/player/groundfall.prefab", bp.transform.position); } } else { //Unable to detect ground. Let player fall. rb.useGravity = true; rb.drag = 1f; rb.velocity = new Vector3(bp.transform.forward.x * 15, 11, bp.transform.forward.z * 15); } //Player has been grounded flying = false; if (BN != null) { // BN.Warp(Home); BN.CanUseNavMesh = !settings.stationary; } //mounts bot onto nearby mount point if set. if (settings.mount) { plugin.MountBot(bp); } } //Collider to remove parachute private void OnCollisionEnter(Collision col) { //Ignore collider if not flying if (!flying) return; //Attach to navmesh ClipGround(); } bool CheckColliders() { //Scans the area foreach (Collider col in Physics.OverlapSphere(bp.transform.position, plugin.ColliderDistance, plugin.parachuteLayer)) { //Converts each collider to a string string thisobject = col.gameObject.ToString(); //Checks if collider contains partial names if (thisobject.Contains("modding") || thisobject.Contains("props") || thisobject.Contains("structures") || thisobject.Contains("building core")) { return true; } //Check if its a base entity BaseEntity baseEntity = col.gameObject.ToBaseEntity(); if (baseEntity != null && (baseEntity == bp || baseEntity == bp.GetComponent())) { return false; } else { return true; } } return false; } void GoHome() { //Remove events from bot, Set to normal movement speed, Make them act if they are going to cover if (bot != null) { bot.Brain.Navigator.SetDestination(Home, BaseNavigator.NavigationSpeed.Normal); } else if (SBrain != null) { SBrain.Navigator.SetDestination(Home, BaseNavigator.NavigationSpeed.Normal); } //Change flags bp.SetPlayerFlag(BasePlayer.PlayerFlags.Relaxed, true); bp.SetPlayerFlag(BasePlayer.PlayerFlags.Aiming, false); } void ForgetAll(int resetdelay) { //Clear all memory and disable senses for a little bit to gain some distance. if (bot != null) { bot.Brain.Senses.Memory.Targets.Clear(); bot.Brain.Senses.Memory.Threats.Clear(); bot.Brain.Senses.Memory.All.Clear(); bot.Brain.Senses.Memory.LOS.Clear(); bot.Brain.HostileTargetsOnly = settings.peacekeeper; bot.Brain.SetEnabled(false); Invoke("ResetSenses", resetdelay); } else if (SBrain != null) { SBrain.Senses.Memory.Targets.Clear(); SBrain.Senses.Memory.Threats.Clear(); SBrain.Senses.Memory.All.Clear(); SBrain.Senses.Memory.LOS.Clear(); SBrain.HostileTargetsOnly = settings.peacekeeper; SBrain.SetEnabled(false); Invoke("ResetSenses", resetdelay); } } void ResetSenses() { //Delayed enabling of senses if (bot != null) { bot.Brain.SetEnabled(true); } else if (SBrain != null) { SBrain.SetEnabled(true); } } void HomeChecks() { //Dont try to move if bot is sitting. if (bp.isMounted) { //Do Some Seated stuff return; } //Return home if no more activity. if (LastInteraction >= settings.cooldown && Vector3.Distance(bp.transform.position, Home) > settings.roamrange && !flying) { //Remove details from bot of its last taget bp.LastAttackedDir = Home; bp.lastAttacker = null; //If its still not with in its roam distance after 5 checks force warp it back. if (FailedHomes >= 5) { if (plugin.DebugInfo) plugin.Puts(bp.displayName + " Forced Home"); BN.Agent.Warp(Home); FailedHomes = 0; LastInteraction = 0; GoHome(); return; } FailedHomes++; //Reset interaction and movement LastInteraction = 0; if (plugin.DebugInfo) plugin.Puts(bp.displayName + " Going Home"); try { ForgetAll(10); GoHome(); return; } catch { bp.Kill(); return; } } //No interaction this tick LastInteraction++; } void AttackLogic(BasePlayer AttackPlayer) { //Checks if its a gun or melee var gun = NPC.GetGun(); AttackEntity AE = NPC?.GetHeldEntity() as AttackEntity; //Gives them a rock if they have nothing if (AE == null) { ItemDefinition itemDefinition = ItemManager.FindItemDefinition("rock"); Item rock = ItemManager.Create(itemDefinition, 1, 0); NPC.GiveItem(rock); NPC.UpdateActiveItem(rock.uid); } if (gun == null && AE != null) { if (plugin.DebugInfo) plugin.Puts("Melee Trigger " + AE.ShortPrefabName); switch (AE.ShortPrefabName) { case "flamethrower.entity": InvokeRepeating("use", 0f, 0.025f); plugin.BotMeleeAttack(NPC, AttackPlayer, AE, Rust.DamageType.Heat, 5, "assets/bundled/prefabs/fx/impacts/additive/fire.prefab", 100); return; case "watergun.entity": //Need Fix LiquidWeapon LW = AE as LiquidWeapon; LW.AutoPump = true; plugin.FireWaterGun(NPC, LW); return; case "waterpistol.entity": //Need Fix LiquidWeapon LW2 = AE as LiquidWeapon; LW2.AutoPump = true; plugin.FireWaterGun(NPC, LW2); return; case "grenade.f1.entity": AE.repeatDelay = 5f; plugin.ServerThrow(AttackPlayer.transform.position, AE as ThrownWeapon, NPC); return; case "grenade.beancan.entity": AE.repeatDelay = 5f; plugin.ServerThrow(AttackPlayer.transform.position, AE as ThrownWeapon, NPC); return; case "smoke_grenade.weapon": AE.repeatDelay = 5f; plugin.ServerThrow(AttackPlayer.transform.position, AE as ThrownWeapon, NPC); return; case "snowball.entity": AE.repeatDelay = 5f; return; }; BaseMelee weapon = NPC?.GetHeldEntity() as BaseMelee; if (weapon == null) { return; } //Do melee slash if in its reach if (!AE.HasAttackCooldown() && Vector3.Distance(AttackPlayer.transform.position, NPC.transform.position) <= weapon.maxDistance) { //melee hit if (plugin.DebugInfo) plugin.Puts("Trigger Melee Attack"); if (NPC.MeleeAttack()) { //Apply Damage plugin.BotMeleeAttack(NPC, AttackPlayer, weapon, Rust.DamageType.Slash, weapon.TotalDamage()); } } } else { switch (NPC.GetHeldEntity().ShortPrefabName) { //Rocket Launcher Logic case "rocket_launcher.entity": gun.repeatDelay = 5f; plugin.EmulatedFire(gun, NPC, AttackPlayer); return; //Grenade Launcher Logic case "mgl.entity": gun.repeatDelay = 2f; plugin.EmulatedFire(gun, NPC, AttackPlayer); return; } if (gun != null) { //Do gun trigger if (plugin.DebugInfo) plugin.Puts("Trigger Shoot"); float triggertime = UnityEngine.Random.Range(gun.attackLengthMin, gun.attackLengthMax); if (triggertime == -1) { //Some guns have -1 as there attacklength so give them one based on ammo count triggertime = gun.primaryMagazine.contents / 4; if (triggertime == 0) triggertime = 1f; } InvokeRepeating("use", 0f, 0.025f); } //reload gun if less than 1 shot left. int ammo = 0; try { ammo = gun.primaryMagazine.contents; } catch { return; } if (ammo < 1) { if (plugin.DebugInfo) plugin.Puts("Trigger Reload"); NPC.AttemptReload(); } } } void use() { if (NPC != null) { try { NPC.GetHeldEntity().ServerUse(NPC.damageScale); } catch { } } //Stop Invoke CancelInvoke("use"); } void SwitchGun() { NPC.UpdateActiveItem(Gun.uid); NPC.SendNetworkUpdateImmediate(); Healing = false; } void _tick() { //Check if bot exsists if (bp == null) { Destroy(this); return; } //Fall back check if bot falls though map while parachuting with out hitting a navmesh. if (flying) { if (TerrainMeta.HeightMap.GetHeight(bp.transform.position) >= bp.transform.position.y || CheckColliders()) { ClipGround(); return; } } //Stops attacking when in safe zone if (bp.InSafeZone()) { ForgetAll(30); GoHome(); return; } //Dont run check if bot is downed or dead if (bp.IsCrawling() || bp.IsDead() || bp.IsWounded()) { return; } //Check if bot should be stationary but has moved. if (settings.stationary) { if (Vector3.Distance(bp.transform.position, Home) > 1f) { //Force back to spawners location BN.Agent.Warp(Home); } } //Heal logic if (NPC.health < NPC.MaxHealth() / 2 && !Healing && settings.canheal) { if (plugin.DebugInfo) plugin.Puts("Bot is Healing"); //Store current held gun if not medical item if (NPC.GetHeldEntity() is MedicalTool) { return; } try { //Store gun Gun = NPC.GetHeldEntity().GetItem(); } catch { //Failed to store gun so end function return; } //Check bot for med items foreach (var item in NPC.inventory.containerBelt.itemList.ToList()) { if (item.GetHeldEntity() is MedicalTool) { //Switch to the meds NPC.UpdateActiveItem(item.uid); NPC.inventory.UpdatedVisibleHolsteredItems(); //use meds MedicalTool meds = NPC.GetHeldEntity() as MedicalTool; if (meds != null) { Healing = true; meds.ServerUse(); } //Switch back to gun Invoke("SwitchGun", 4f); break; } } return; } //Find Targeted Player BasePlayer AttackPlayer = GetBestTarget(); //Fallback funding players in CQ slower method if nothing found if (AttackPlayer == null) { List PlayerScan = new List(); Vis.Entities(bp.transform.position, 10, PlayerScan); foreach (BasePlayer entity in PlayerScan) { //Stops shooting though walls/doors if (BasePlayer.activePlayerList.Contains(entity) && bp.IsVisibleAndCanSee(entity.eyes.position)) { //Found a player to attack AttackPlayer = entity; break; } } } //No players with in range do nothing. if (AttackPlayer == null) { HomeChecks(); return; } //Reset Home Checking FailedHomes = 0; LastInteraction = 0; //Make sure its not another bot if (!AttackPlayer.IsNpc) { //Peacekeeper bypass attack code. if (settings.peacekeeper && !AttackPlayer.IsHostile()) { return; } //Sleeper Protection if (plugin.SleepProtect && AttackPlayer.IsSleeping()) { return; } //Adjust Chance faces the right way. //BN.SetFacingDirectionOverride(AttackPlayer.transform.position += new Vector3(-UnityEngine.Random.Range(0.0f, 1.0f), -UnityEngine.Random.Range(0.0f , 1.0f), -UnityEngine.Random.Range(0.0f, 1.0f))); ; //Turn to the player thats the target if (UnityEngine.Random.Range(1, 101) <= settings.accuracy) { BN.SetFacingDirectionEntity(AttackPlayer); if (plugin.IgnoredShots.Contains(bp.userID)) plugin.IgnoredShots.Remove(bp.userID); } else { if (!plugin.IgnoredShots.Contains(bp.userID)) plugin.IgnoredShots.Add(bp.userID); } //Attack AttackLogic(AttackPlayer); } } } private void OnServerInitialized(bool initial) { plugin = this; //Delay startup if fresh server boot. Helps hooking on slow servers if (initial) { Fstartup(); return; } //Startup plugin Startup(); } private void Fstartup() { //Waits for fully loaded before running script to help first startup performance. timer.Once(10f, () => { try { if (Rust.Application.isLoading) { //Still starting so run a timer again in 10 sec to check. Fstartup(); return; } } catch { } //Starup script now. Startup(); }); } private void Startup() { //Clears clocksettings incase its triggered reload. NPC_Spawners.Clear(); uint placeholderid = StringPool.Get(prefabplaceholder); //Find All NPCSpawners in the map for (int i = World.Serialization.world.prefabs.Count - 1; i >= 0; i--) { PrefabData prefabdata = World.Serialization.world.prefabs[i]; //Check the prefab datas category since thats where customprefabs names are stored if (prefabdata.id == placeholderid && prefabdata.category.Contains("BMGBOT")) { //Pull settings from prefab name string settings = prefabdata.category.Split(':')[1].Replace("\\", ""); //Check if it is already in dict if (!NPC_Spawners.ContainsKey(prefabdata.position)) { BotsSettings bs = new BotsSettings(); if (settings != null) { //Settings are seperated by a fullstop string[] ParsedSettings = settings.Split('.'); foreach (string keyword in ParsedSettings) { if (keyword.ToLower().Contains("stationary")) { bs.stationary = true; } else if (keyword.ToLower().Contains("name")) { try { bs.name = keyword.Split('=')[1]; } catch { bs.name = ""; } } else if (keyword.ToLower().Contains("kit")) { try { bs.kitname = keyword.Split('=')[1]; } catch { bs.kitname = ""; } } else if (keyword.ToLower().Contains("parachute")) { bs.parachute = true; } else if (keyword.ToLower().Contains("replace")) { bs.replaceitems = true; } else if (keyword.ToLower().Contains("health")) { try { bs.health = int.Parse(keyword.Split('=')[1]); } catch { bs.health = 0; } } else if (keyword.ToLower().Contains("attack")) { try { bs.AttackRange = int.Parse(keyword.Split('=')[1]); } catch { bs.AttackRange = 30; } } else if (keyword.ToLower().Contains("roam")) { try { bs.roamrange = int.Parse(keyword.Split('=')[1]); } catch { bs.roamrange = 30; } } else if (keyword.ToLower().Contains("cooldown")) { try { bs.cooldown = int.Parse(keyword.Split('=')[1]); } catch { bs.cooldown = 15; } } else if (keyword.ToLower().Contains("peacekeeper")) { bs.peacekeeper = true; } else if (keyword.ToLower().Contains("radiooff")) { bs.radiooff = true; } else if (keyword.ToLower().Contains("taunt")) { bs.taunts = true; } else if (keyword.ToLower().Contains("height")) { try { bs.height = int.Parse(keyword.Split('=')[1]); } catch { bs.height = 0; } } else if (keyword.ToLower().Contains("steamicon")) { try { bs.steamicon = ulong.Parse(keyword.Split('=')[1]); } catch { bs.steamicon = 0; } } else if (keyword.ToLower().Contains("speed")) { try { bs.speed = int.Parse(keyword.Split('=')[1]); } catch { bs.speed = 0; } } else if (keyword.ToLower().Contains("accuracy")) { try { bs.accuracy = int.Parse(keyword.Split('=')[1]); } catch { bs.accuracy = 50; } } else if (keyword.ToLower().Contains("damageScale")) { try { bs.damageScale = int.Parse(keyword.Split('=')[1]); } catch { bs.damageScale = 100; } } else if (keyword.ToLower().Contains("strip")) { bs.strip = true; } else if (keyword.ToLower().Contains("mount")) { bs.mount = true; } else if (keyword.ToLower().Contains("killnotice")) { bs.killnotice = true; } else if (keyword.ToLower().Contains("apcattack")) { bs.apcattack = true; } else if (keyword.ToLower().Contains("canheal")) { bs.canheal = true; } } } //Create Dictornary reference if (!NPC_Spawners.ContainsKey(prefabdata.position)) { NPC_Spawners.Add(prefabdata.position, bs); } } } } //Set plugin as fully ready HasLoadedSpawner = true; //Clean up place holders RemoveAllPlaceHolders(); //Outputs debug info Puts("Found " + NPC_Spawners.Count.ToString() + " NPC Spawners"); } private void Unload() { plugin = null; //removes the script foreach (var script in GameObject.FindObjectsOfType()) { script.EnsureDismounted(); foreach (var af in script.GetComponentsInChildren()) { UnityEngine.Object.DestroyImmediate(af); //Kill bot since wont get re hooked if its moved from its spawner script.Kill(); } } } void OnWorldPrefabSpawned(GameObject gameObject, string str) { //Creates a list of prefab used as placeholders to make the prefab group. if (gameObject.name == prefabplaceholder) { PlaceHolders.Add(gameObject.GetComponent()); } //Fix invalid prefab stopping server starting BaseEntity component = gameObject.GetComponent(); if (component != null) { if (component.OwnerID == 0 && gameObject.transform.position == MapLockLocation && MapLockLocation != new Vector3(0, 0, 0)) { if (DebugInfo) Puts("Removed Invalid Prefab @ " + MapLockLocation); //Remove Invalid Prefab thats used as somewhat protection. component.Kill(); return; } //Door collider shrink but not destroy to stop bots shooting though then. if (FixDoors) { Door doorfix = component.GetComponent(); if (doorfix != null) { if (DebugInfo) Puts("Door Fixed @ " + doorfix.transform.position.ToString()); //Scales door X to 0.1 so it still has some thickness to stop bot bullet and vison but not so thick player hits on it. doorfix.transform.localScale = new Vector3(0.1f, doorfix.transform.localScale.y, doorfix.transform.localScale.z); } } } } void OnEntityTakeDamage(BaseCombatEntity entity, HitInfo info) { if (info != null && info.InitiatorPlayer != null && entity != null) { BasePlayer player = info.InitiatorPlayer; if (IgnoredShots.Contains(player.userID)) { info.damageTypes.ScaleAll(0.1f); IgnoredShots.Remove(player.userID); } } } void OnEntityDeath(BaseEntity entity, HitInfo info) { try { if (entity == null || info == null) return; //Dismount bots on seat break BaseMountable seat = entity.GetComponent(); if (seat != null) { if (seat.GetMounted().IsNpc) { seat.DismountAllPlayers(); } } //Get player BasePlayer player = null; player = entity.ToPlayer(); if (player == null) return; //Get who was the attacker BasePlayer attacker = null; attacker = player.lastAttacker.ToPlayer(); if (attacker == null) return; //Failsafe so while loop cant get stuck going on for ever int failsafe = 0; //Taunt bot kills if (NPC_Bots.ContainsKey(attacker.userID)) { if (NPC_Spawners.ContainsKey(NPC_Bots[attacker.userID])) { if (NPC_Spawners[NPC_Bots[attacker.userID]].taunts) { //Pick a taunt at randm making sure not to be a repeat if (Killed == null) return; int seed = 0; seed = Random.Range(0, Killed.Length - 1); while (seed == lasttaunt && failsafe < 10) { failsafe++; seed = Random.Range(0, Killed.Length - 1); } lasttaunt = seed; //Send taunt to chat. CreateTaunt(Killed[seed].Replace("{N}", player.displayName), attacker, NPC_Spawners[NPC_Bots[attacker.userID]].steamicon); return; } } } //taunt bot dies if (NPC_Bots.ContainsKey(player.userID)) { if (NPC_Spawners.ContainsKey(NPC_Bots[player.userID])) { //Public bot death annoucements if (NPC_Spawners[NPC_Bots[player.userID]].killnotice) { //Send kill announcement to chat. CreateAnouncment(attacker.displayName + " Killed " + player.name + " With " + info.damageTypes.GetMajorityDamageType()); } if (NPC_Spawners[NPC_Bots[player.userID]].taunts) { //Pick a taunt at randm making sure not to be a repeat int seed = Random.Range(0, Dead.Length - 1); while (seed == lasttaunt && failsafe < 10) { failsafe++; seed = Random.Range(0, Dead.Length - 1); } lasttaunt = seed; //Send taunt to chat. CreateTaunt(Dead[seed].Replace("{N}", attacker.displayName), player.ToPlayer(), NPC_Spawners[NPC_Bots[player.userID]].steamicon); return; } } } } catch { } } void OnEntitySpawned(BaseNetworkable entity) { //Fix console spam BaseNavigator baseNavigator = entity.GetComponent(); //Checks if has navigator AI if (baseNavigator != null) { //temp position Vector3 pos; //Checks if its within the default navmesh scan settings if (!baseNavigator.GetNearestNavmeshPosition(entity.transform.position + (Vector3.one * 2f), out pos, (baseNavigator.IsSwimming() ? 30f : 6f))) { //Sets as stationary if (DebugInfo) Puts("No Navmesh found below Bot @ " + entity.transform.position.ToString() + " bot will be frozen in place"); baseNavigator.CanUseNavMesh = false; } } //Cast as a NPCPlayer NPCPlayer bot = entity as NPCPlayer; //Check if NPC if (bot != null) { //Checks if NPC_Spawner list has been filled if (!HasLoadedSpawner) Startup(); //Checks bots against NPC_Spawner NextFrame(() => { CheckBot(bot); }); } //Check corpses if (entity.ShortPrefabName == "frankensteinpet_corpse") { //Delay until next frame to allow it to be spawned NextFrame(() => { LootableCorpse corpse = entity as LootableCorpse; if (corpse != null) { UpdateCorpse(corpse); corpse.SendNetworkUpdateImmediate(); } }); } else if (entity.ShortPrefabName == "scientist_corpse") { //Delay until next frame to allow it to be spawned NextFrame(() => { LootableCorpse corpse = entity as LootableCorpse; if (corpse != null) { UpdateCorpse(corpse); corpse.SendNetworkUpdateImmediate(); } }); } } void UpdateCorpse(LootableCorpse corpse) { //Checks if its a NPC if (NPC_Bots.ContainsKey(corpse.playerSteamID)) { //Checks if settings from the spawner if (NPC_Spawners.ContainsKey(NPC_Bots[corpse.playerSteamID])) { //If its set to replace items or strip them if (NPC_Spawners[NPC_Bots[corpse.playerSteamID]].replaceitems || NPC_Spawners[NPC_Bots[corpse.playerSteamID]].strip) { //Empty default items off corpse for (int i = 0; i < 3; i++) { corpse.containers[i].Kill(); } //Only remove items if (NPC_Spawners[NPC_Bots[corpse.playerSteamID]].strip) return; //Checks list of saved items from kit if (NPC_Items.ContainsKey(corpse.playerSteamID)) { if (DebugInfo) Puts("Moving Items"); foreach (Botsinfo items in NPC_Items[corpse.playerSteamID]) { corpse.containers[0].AddItem(items.idef, items.item_ammount, items.item_skin); } //Emptys kit item list NPC_Items.Remove(corpse.playerSteamID); } //Wait 1 sec since RE and other plugins might be adding stuff on the next frame already. timer.Once(1f, () => { if (corpse == null) { return; } //Remove the delayed outfits rust edit adds to frankenstine and scientist corpses foreach (ItemContainer ic in corpse.containers) { foreach (Item it in ic.itemList.ToArray()) { if (it.info.shortname.Contains("halloween.mummysuit") || it.info.shortname.Contains("scarecrow.suit") || it.info.shortname.Contains("hazmatsuit_scientist")) { it.Remove(); } } } }); } //Update corpses name corpse.playerName = NPC_Spawners[NPC_Bots[corpse.playerSteamID]].name; //Remove stored bot since its dead. NPC_Bots.Remove(corpse.playerSteamID); } } } void RemoveAllPlaceHolders() { //Go though each placeholder and make sure its with in range of the NPC Spawner before removing it. foreach (BaseEntity PH in PlaceHolders) { if (PH != null) { foreach (KeyValuePair Spawners in NPC_Spawners) { if (Vector3.Distance(PH.transform.position, Spawners.Key) < ScanDistance) { try { PH.Kill(); } catch { } } } } } } //Sends message to all active players under a steamID void CreateAnouncment(string msg) { foreach (BasePlayer current in BasePlayer.activePlayerList.ToArray()) { if (current.IsConnected) { rust.SendChatMessage(current, "Death", msg, AnnouncementIcon.ToString()); } } } //Sends chat message to all active players. void CreateTaunt(string msg, BasePlayer player, ulong steamid) { foreach (BasePlayer current in BasePlayer.activePlayerList.ToArray()) { if (current.IsConnected) { rust.SendChatMessage(current, "" + player.displayName + ":", msg, steamid.ToString()); } } } void CheckBot(NPCPlayer bot) { if (bot == null) return; //Loop all the NPC_Spawners that have been hooked. for (int i = 0; i < NPC_Spawners.Count; i++) { //Checks with in 5f range of spawner since bot could of moved slightly. if (Vector3.Distance(NPC_Spawners.ElementAt(i).Key, bot.transform.position) < 5f) { if (!NPC_Bots.ContainsKey(bot.userID)) { NPC_Bots.Add(bot.userID, NPC_Spawners.ElementAt(i).Key); //Attach script if its not already attached if (bot.gameObject.GetComponent() == null) { bot.gameObject.AddComponent(); } } } } //Murdurer Fix Delay so it can spawn chainsaw then start it so it will do damage. timer.Once(2f, () => { //checks if it has a chainsaw Chainsaw cs = bot.GetHeldEntity() as Chainsaw; if (cs != null) { //Turn on chainsaw cs.SetFlag(Chainsaw.Flags.On, true); cs.SendNetworkUpdateImmediate(); } }); } void ParachuteSuiside(BasePlayer bot) { //Destroys parachute after 20 secs incase bot gets stuck in trees/buildings timer.Once(ChuteSider, () => { bool Destroyed = false; foreach (var child in bot.children.Where(child => child.name.Contains("parachute"))) { child.SetParent(null); child.Kill(); Destroyed = true; break; } if (Destroyed) { Effect.server.Run("assets/bundled/prefabs/fx/player/groundfall.prefab", bot.transform.position); } }); } //Do attack damage and sfx slightly delayed to match with time swing takes to happen void BotMeleeAttack(NPCPlayer bot, BasePlayer AttackPlayer, BaseEntity weapon, Rust.DamageType damagetype, float Damage, string sfx = "assets/bundled/prefabs/fx/headshot.prefab", int HeadshotPercentage = 10) { if (bot == null || AttackPlayer == null || weapon == null) return; //Create a delay for the animation float delay = 0.2f; try { delay = (weapon as BaseMelee).aiStrikeDelay; } catch { try { delay = (weapon as AttackEntity).animationDelay; } catch { } } timer.Once(delay, () => { float range = 0.8f; try { range = (weapon as BaseMelee).maxDistance; } catch { try { range = (weapon as AttackEntity).effectiveRange; } catch { } } //Check weapons reach. if (Vector3.Distance(bot.transform.position, AttackPlayer.transform.position) < range) { //Apply damage and play SFX //AttackPlayer.Hurt(Damage, damagetype, bot, true); if (UnityEngine.Random.Range(1, 101) <= HeadshotPercentage) Effect.server.Run(sfx, AttackPlayer.transform.position); } }); } //Blocks shooting when under attack range on setup bots except heavy and junkpile for some reason. private object OnNpcTarget(NPCPlayer npc, BaseEntity entity) { if (entity == null || npc == null) return null; if (NPC_Bots.ContainsKey(npc.userID)) { if (NPC_Spawners.ContainsKey(NPC_Bots[npc.userID])) { if (Vector3.Distance(npc.transform.position, entity.transform.position) >= NPC_Spawners[NPC_Bots[npc.userID]].AttackRange + 10) { return true; } //Cast target to baseplayer to check some conditions BasePlayer TargetedPlayer = entity.ToPlayer(); if (TargetedPlayer == null) { return null; } //Block targeting of non-hostile players from peacekeeper bots. if (NPC_Spawners[NPC_Bots[npc.userID]].peacekeeper && !TargetedPlayer.IsHostile()) { return true; } //Protects sleepers if (SleepProtect && TargetedPlayer.IsSleeping()) { return true; } } } return null; } //Hook to disable radio private object OnNpcRadioChatter(ScientistNPC npc) { //Check if a custom bot if (NPC_Bots.ContainsKey(npc.userID)) { //check if has setting if (NPC_Spawners.ContainsKey(NPC_Bots[npc.userID])) { //check radio status and disable radio if set if (NPC_Spawners[NPC_Bots[npc.userID]].radiooff) { //no chatter return true; } } } //Normal bahavour return null; } //Fix for bradly going nuts on NPCs private object CanBradleyApcTarget(BradleyAPC apc, BaseEntity baseentity) { if (baseentity == null) return null; BasePlayer player = baseentity.ToPlayer(); if (apc == null && player == null) return null; if (NPC_Bots.ContainsKey(player.userID)) { if (NPC_Spawners.ContainsKey(NPC_Bots[player.userID])) { //Checks if doesnt have flag to allow NPC to attack if (!NPC_Spawners[NPC_Bots[player.userID]].apcattack) return false; } } return null; } //Fix for NPCs with rocket launchers just firing at there feet. private void EmulatedFire(BaseProjectile _ProectileWeapon, NPCPlayer player, BasePlayer targetplayer) { if (_ProectileWeapon == null || _ProectileWeapon.HasAttackCooldown()) return; //Get distance from bot to player to work out rocket arch float AimDistance = Vector3.Distance(player.transform.position, targetplayer.transform.position); //Get direction of launcher Vector3 vector3 = _ProectileWeapon.MuzzlePoint.transform.forward; //Get height of launcher Vector3 position = _ProectileWeapon.MuzzlePoint.transform.position + (Vector3.up * 1.6f); //Create a new rocket BaseEntity entity = null; switch (_ProectileWeapon.ShortPrefabName) { case "rocket_launcher.entity": entity = GameManager.server.CreateEntity("assets/prefabs/ammo/rocket/rocket_basic.prefab", position, player.eyes.GetLookRotation()); break; case "mgl.entity": entity = GameManager.server.CreateEntity("assets/prefabs/ammo/40mmgrenade/40mm_grenade_he.prefab", position, player.eyes.GetLookRotation()); AimDistance += 5f; break; default: return; } if (entity == null) return; //Get rockets settings var proj = entity.GetComponent(); if (proj == null) return; //Set creator of rocket to bot entity.creatorEntity = player; //Set fly angle proj.InitializeVelocity(Quaternion.Euler(vector3) * entity.transform.forward * AimDistance); //Adjust explosive delay TimedExplosive rocketExplosion = entity.GetComponent(); if (rocketExplosion != null) { rocketExplosion.timerAmountMin = 1; rocketExplosion.timerAmountMax = 15; } //Make shot happen. entity.Spawn(); } public void FireWaterGun(BasePlayer ownerPlayer, BaseEntity wep) { //Get as item global::Item item = wep.GetItem(); if (item == null) { return; } //Creates water ItemDefinition itemDefinition = ItemManager.FindItemDefinition("water"); //Get direction Ray ray = ownerPlayer.eyes.BodyRay(); //Draw water line Debug.DrawLine(ray.origin, ray.origin + ray.direction * 10f, Color.blue, 1f); //Do damage RaycastHit hitInfo; Physics.Raycast(ray, out hitInfo, 10f, 1218652417); List damage = new List(); WaterBall.DoSplash(hitInfo.point, 2f, itemDefinition, 10); DamageUtil.RadiusDamage(ownerPlayer, wep.LookupPrefab(), hitInfo.point, 0.15f, 0.15f, damage, 131072, true); } //Make NPCs able to throw public void ServerThrow(Vector3 targetPosition, ThrownWeapon wep, NPCPlayer player) { if (wep == null || wep.HasAttackCooldown()) return; //Get aim direction Vector3 position = player.eyes.position; Vector3 vector3 = player.eyes.BodyForward(); float AimDistance = 1f; //Trigger throw server code wep.SignalBroadcast(BaseEntity.Signal.Throw, string.Empty); //Create the nade BaseEntity entity = GameManager.server.CreateEntity(wep.prefabToThrow.resourcePath, position, Quaternion.LookRotation(wep.overrideAngle == Vector3.zero ? -vector3 : wep.overrideAngle)); if ((UnityEngine.Object)entity == (UnityEngine.Object)null) return; //Set owner entity.creatorEntity = (BaseEntity)player; //Get throw arch Vector3 aimDir = vector3 + Quaternion.AngleAxis(10f, Vector3.right) * Vector3.up; float f = 6f; if (float.IsNaN(f)) { aimDir = vector3 + Quaternion.AngleAxis(20f, Vector3.right) * Vector3.up; f = 6f; if (float.IsNaN(f)) f = 5f; } entity.SetVelocity(aimDir * f * AimDistance); if ((double)wep.tumbleVelocity > 0.0) entity.SetAngularVelocity(new Vector3(UnityEngine.Random.Range(-1f, 1f), UnityEngine.Random.Range(-1f, 1f), UnityEngine.Random.Range(-1f, 1f)) * wep.tumbleVelocity); //Make sure doesnt dud DudTimedExplosive dud = entity.GetComponent(); if (dud != null) dud.dudChance = 0f; entity.Spawn(); } public static bool IsFemale(ulong userID) { //Save current random state UnityEngine.Random.State state = UnityEngine.Random.state; //initilise in a known state so we already know the outcome of the random generator //Feed userid as the seed UnityEngine.Random.InitState((int)((uint)4332 + userID)); //Determin gender bool Gender = (UnityEngine.Random.Range(0f, 1f) > 0.5f); //Reset state back to a random unknown state we saved. UnityEngine.Random.state = state; return Gender; } //Scans around the bot for seats, Put the bot in the closest seat to it. public void MountBot(BasePlayer bot) { List Seats = new List(); Vis.Entities(bot.transform.position, MountScan, Seats); BaseMountable closest_seat = null; //Finds closest seat thats not already mounted foreach (BaseMountable seat in Seats) { if (seat.HasFlag(BaseEntity.Flags.Busy)) continue; if (closest_seat == null) closest_seat = seat; if (Vector3.Distance(bot.transform.position, seat.transform.position) <= Vector3.Distance(bot.transform.position, closest_seat.transform.position)) closest_seat = seat; } //Trys to mount seat if (closest_seat != null) { closest_seat.GetComponent().AttemptMount(bot); closest_seat.SendNetworkUpdateImmediate(); bot.SendNetworkUpdateImmediate(); if (plugin.DebugInfo) plugin.Puts(bot.displayName + " Forced into Seat @ " + closest_seat.transform.position.ToString()); } } private bool IsKit(string kit) { //Call kit plugin to check if its valid kit var success = Kits?.Call("isKit", kit); if (success == null || !(success is bool)) { return false; } return (bool)success; } void BotSkin(NPCPlayer bot, string Skin, bool replacement) { //Remove Default kit ItemManager.DoRemoves(); foreach (Item i in bot.inventory.AllItems()) { i.Remove(); } ItemManager.DoRemoves(); //Try apply the kit Kits?.Call("GiveKit", bot, Skin); //Trys to equip stuff after a delay for kits plugin to of ran Item projectileItem = null; //Find first gun timer.Once(0.5f, () => { foreach (var item in bot.inventory.containerBelt.itemList.ToList()) { if (item.GetHeldEntity() is BaseProjectile) { projectileItem = item; break; } //Move medial items out of hot bar if (item.GetHeldEntity() is MedicalTool) { if (bot.inventory.containerBelt.GetSlot(5) != null) { bot.inventory.containerBelt.GetSlot(5).MoveToContainer(bot.inventory.containerMain); } item.MoveToContainer(bot.inventory.containerBelt, 5); continue; } } if (projectileItem != null) { //pull out gun. bot.UpdateActiveItem(projectileItem.uid); bot.inventory.UpdatedVisibleHolsteredItems(); } else { //Find a melee weapon in the belt foreach (var item in bot.inventory.containerBelt.itemList.ToList()) { //Move medial items out of hot bar if (item.GetHeldEntity() is MedicalTool) { if (bot.inventory.containerBelt.GetSlot(5) != null) { bot.inventory.containerBelt.GetSlot(5).MoveToContainer(bot.inventory.containerMain); } item.MoveToContainer(bot.inventory.containerBelt, 5); continue; } if (item.GetHeldEntity() is BaseMelee) { projectileItem = item; break; } } //Try pull out active weapon try { bot.UpdateActiveItem(projectileItem.uid); bot.inventory.UpdatedVisibleHolsteredItems(); } catch { } } //Try get gun ready. try { timer.Once(1f, () => { (bot as NPCPlayer).AttemptReload(); }); } catch { } //Only do this if bot wants items replaced on the corpse if (replacement) { timer.Once(0.5f, () => { //Update bot item list for corpse use List items = new List(); foreach (Item item in bot.inventory.containerBelt.itemList) { if (item.info != null) { Botsinfo bi = ProcessKitItem(item); if (!items.Contains(bi)) items.Add(bi); } } foreach (Item item in bot.inventory.containerMain.itemList) { if (item.info != null) { Botsinfo bi = ProcessKitItem(item); if (!items.Contains(bi)) items.Add(bi); } } foreach (Item item in bot.inventory.containerWear.itemList) { if (item.info != null) { Botsinfo bi = ProcessKitItem(item); if (!items.Contains(bi)) items.Add(bi); } } if (NPC_Items.ContainsKey(bot.userID)) { NPC_Items[bot.userID] = items; } else { NPC_Items.Add(bot.userID, items); } }); } }); } } }