using Facepunch; using Newtonsoft.Json; using Newtonsoft.Json.Linq; using Oxide.Core; using Oxide.Core.Libraries.Covalence; using Oxide.Core.Plugins; using Rust; using System; using System.Linq; using System.Collections; using System.Globalization; using System.Collections.Generic; using UnityEngine; using UnityEngine.AI; namespace Oxide.Plugins { [Info("NPC Grenades", "ZEODE", "1.2.7")] [Description("F1 grenades spawn various NPCs when thrown.")] public class NPCGrenades: CovalencePlugin { #region Plugin References [PluginReference] private Plugin Friends, Clans, Kits; #endregion #region Consts private static NPCGrenades plugin; private static System.Random random = new System.Random(); private static VersionNumber previousVersion; public const int f1GrenadeId = 143803535; public const ulong scientistSkinID = 2640541557; public const ulong heavySkinID = 2640541496; public const ulong juggernautSkinID = 2647297156; public const ulong tunnelSkinID = 2676146196; public const ulong underwaterSkinID = 2676146329; public const ulong murdererSkinID = 2643502595; public const ulong scarecrowSkinID = 2647297210; public const ulong mummySkinID = 2643385137; public const ulong bearSkinID = 2647301111; public const ulong polarbearSkinID = 2868239755; public const ulong wolfSkinID = 2647303718; public const ulong boarSkinID = 2643502513; public const ulong stagSkinID = 2647297256; public const ulong chickenSkinID = 2647297056; public const ulong bradleySkinID = 2643385052; public const string permScientist = "npcgrenades.scientist"; public const string permHeavy = "npcgrenades.heavy"; public const string permJuggernaut = "npcgrenades.juggernaut"; public const string permTunnel = "npcgrenades.tunnel"; public const string permUnderwater = "npcgrenades.underwater"; public const string permMurderer = "npcgrenades.murderer"; public const string permScarecrow = "npcgrenades.scarecrow"; public const string permMummy = "npcgrenades.mummy"; public const string permBear = "npcgrenades.bear"; public const string permPolarbear = "npcgrenades.polarbear"; public const string permWolf = "npcgrenades.wolf"; public const string permBoar = "npcgrenades.boar"; public const string permStag = "npcgrenades.stag"; public const string permChicken = "npcgrenades.chicken"; public const string permBradley = "npcgrenades.bradley"; public const string permAdmin = "npcgrenades.admin"; public const string scientistNade = "Scientist Grenade"; public const string heavyNade = "Heavy Scientist Grenade"; public const string juggernautNade = "Juggernaut Grenade"; public const string tunnelNade = "Tunnel Dweller Grenade"; public const string underwaterNade = "Underwater Dweller Grenade"; public const string murdererNade = "Murderer Grenade"; public const string scarecrowNade = "Scarecrow Grenade"; public const string mummyNade = "Mummy Grenade"; public const string bearNade = "Bear Grenade"; public const string polarbearNade = "Polar Bear Grenade"; public const string wolfNade = "Wolf Grenade"; public const string boarNade = "Boar Grenade"; public const string stagNade = "Stag Grenade"; public const string chickenNade = "Chicken Grenade"; public const string bradleyNade = "Bradley Grenade"; public const string scientistPrefab = "assets/rust.ai/agents/npcplayer/humannpc/scientist/scientistnpc_roam.prefab"; public const string heavyPrefab = "assets/rust.ai/agents/npcplayer/humannpc/scientist/scientistnpc_heavy.prefab"; public const string scarecrowPrefab = "assets/prefabs/npc/scarecrow/scarecrow.prefab"; public const string bradleyPrefab = "assets/prefabs/npc/m2bradley/bradleyapc.prefab"; public const string bearPrefab = "assets/rust.ai/agents/bear/bear.prefab"; public const string polarbearPrefab = "assets/rust.ai/agents/bear/polarbear.prefab"; public const string wolfPrefab = "assets/rust.ai/agents/wolf/wolf.prefab"; public const string boarPrefab = "assets/rust.ai/agents/boar/boar.prefab"; public const string stagPrefab = "assets/rust.ai/agents/stag/stag.prefab"; public const string chickenPrefab = "assets/rust.ai/agents/chicken/chicken.prefab"; public const string murdererChatter = "assets/prefabs/npc/murderer/sound/breathing.prefab"; public const string murdererDeath = "assets/prefabs/npc/murderer/sound/death.prefab"; public const string fleshBloodImpact = "assets/bundled/prefabs/fx/impacts/slash/flesh/fleshbloodimpact.prefab"; public const string explosionEffect = "assets/prefabs/weapons/f1 grenade/effects/f1grenade_explosion.prefab"; public const string bradleyExplosion = "assets/prefabs/npc/m2bradley/effects/bradley_explosion.prefab"; public const string chutePrefab = "assets/prefabs/misc/parachute/parachute.prefab"; #endregion #region Language protected override void LoadDefaultMessages() { lang.RegisterMessages(new Dictionary { ["SyntaxPlayer"] = "Invalid syntax, use: /npcnade.give ", ["WrongType"] = "Grenade type \"{0}\" not recognised, please check & try again.", ["Receive"] = "You received {0} {1}(s)!", ["PlayerReceive"] = "Player {0} ({1}) received {2} {3}(s)!", ["Permission"] = "You do not have permission to use {0}!", ["NotEnabled"] = "{0} is not enabled!", ["NotAdmin"] = "You do not have permission to use that command!", ["PlayerNotFound"] = "Can't find a player with the name or ID: {0}", ["PlayersFound"] = "Multiple players found, please be more specific: {0}", ["UnderWater"] = "{0} ({1}) spawned under water and was killed.", ["InSafeZone"] = "{0} ({1}) spawned in as Safe Zone and was killed.", ["Inside"] = "{0} ({1}) spawned inside and was killed.", ["OnStructure"] = "{0} ({1}) spawned on a building and was killed.", ["NadeOnStructure"] = "{0} was thrown on a building and will not spawn.", ["IsInRock"] = "{0} ({1}) spawned inside terrain and was killed.", ["SyntaxConsole"] = "Invalid syntax, use: npcnade.give ", ["InvalidNade"] = "Grenade type \"{0}\" not recognised, please check and try again!" }, this); } private string Lang(string messageKey, string playerID, params object[] args) { return string.Format(lang.GetMessage(messageKey, this, playerID), args); } private void Message(IPlayer player, string messageKey, params object[] args) { var message = Lang(messageKey, player.Id, args); if (config.options.usePrefix) { player.Reply(config.options.chatPrefix + message); } else { player.Reply(message); } } private void Message(BasePlayer player, string messageKey, params object[] args) { if (player is NPCPlayer) { return; } var message = Lang(messageKey, player.UserIDString, args); if (config.options.usePrefix && config.options.chatPrefix != string.Empty) { player.ChatMessage(config.options.chatPrefix + message); } else { player.ChatMessage(message); } } #endregion #region Oxide Hooks private void OnServerInitialized() { plugin = this; } private void Init() { LoadNadeInfo(); permission.RegisterPermission(permAdmin, this); foreach (var item in NadeInfo.Keys) { permission.RegisterPermission(NadeInfo[item].Perm, this); } try { storedData = Interface.Oxide.DataFileSystem.ReadObject(Name); if (storedData == null) { Puts("Data file is blank. Creating default data file."); storedData = new StoredData(); } } catch (Exception ex) { if (ex is JsonSerializationException || ex is NullReferenceException || ex is JsonReaderException) { Puts($"Exception Type: {ex.GetType()}"); Puts("Data file contains errors. Either fix the errors or delete the data file and reload the plugin for default values."); return; } throw; } LoadData(); } private void Unload() { //SaveData(); plugin = null; NadeInfo.Clear(); GrenadeNPCData.Clear(); BaseNpcData.Clear(); BradleyAPCData.Clear(); NPCInventories.Clear(); } private void OnServerSave() { int delay = random.Next(4, 8); timer.Once(delay, () => { SaveData(); }); } private void OnEntityDeath(BaseCombatEntity entity, HitInfo info) { if (entity.skinID != 0 && IsNpcGrenade(entity.skinID)) { NPCPlayer npc = entity as NPCPlayer; if (npc is ScientistNPC || npc is ScarecrowNPC) { if (GrenadeNPCData.ContainsKey(npc.userID) && !NPCInventories.ContainsKey(npc.userID)) { ItemContainer[] source = { npc.inventory.containerMain, npc.inventory.containerWear, npc.inventory.containerBelt }; Inv npcInv = new Inv() { name = npc.displayName, }; NPCInventories.Add(npc.userID, npcInv); for (int i = 0; i < source.Length; i++) { foreach (var item in source[i].itemList) { npcInv.inventory[i].Add(new InvContents { ID = item.info.itemid, amount = item.amount, skinID = item.skin, }); } } } timer.Once(5.0f, () => { RemoveNpcData(npc.userID); }); } else { RemoveNpcData(entity.net.ID.Value); } } } // Below adds proper grenade names to items added via kits/loadouts by plugins which don't specify a custom item display name // This means when players click on the item in inventory it shows the NPC Nade name, which is also needed for checks later on. private void OnItemAddedToContainer(ItemContainer container, Item item) { if (item.skin != 0) { if (item.name == null && IsNpcGrenade(item.skin)) { if (NadeInfo.ContainsKey(item.skin)) { item.name = NadeInfo[item.skin].Name; } } } } // This hook works regardless of whether a Player or NPCPlayer. OnExplosiveThrown only hooks for Player. private void OnExplosiveFuseSet(TimedExplosive explosive, float fuseLength) { var player = explosive.creatorEntity as BasePlayer; if (player == null || !player.IsAlive()) { return; } var item = player.GetActiveItem(); var skin = item.skin; if (item == null || skin == null) { return; } else if (IsNpcGrenade(skin)) { var name = item.name; if (name == null) { if (!NadeInfo.ContainsKey(skin)) { return; } name = NadeInfo[skin].Name; } OnNpcNadeThrown(player, explosive, item); } } private void OnExplosiveDropped(BasePlayer player, BaseEntity entity, Item item) { var skin = item.skin; if (skin == null || skin == 0) { return; } else if (IsNpcGrenade(skin)) { OnNpcNadeThrown(player, entity, item); } } private object OnEntityTakeDamage(BaseCombatEntity entity, HitInfo info) { var skin = entity.skinID; if (info == null || skin == 0) { return null; } else if (info.Initiator == info.HitEntity) { // Stop melee armed NPC injuring themselves which happens when their swing misses the target if (IsNpcGrenade(skin)) { return true; } } if (!config.bradley.bradleyBaseDamage) { var damageType = info.damageTypes.GetMajorityDamageType(); if (entity is BasePlayer) { return null; } else if (damageType == DamageType.Blunt) { if(entity.GetBuildingPrivilege() && info.WeaponPrefab.name == "MainCannonShell") { // Blunt damage from APC cannon shells blocked CancelHit(info); return true; } } else if (damageType == DamageType.Bullet && info.Initiator is BradleyAPC) { // Bullet damage from APC machine gun bullets blocked CancelHit(info); return true; } } return null; } private object OnNpcTarget(BaseEntity entity, BaseEntity target) { var isEntityNade = IsNpcGrenade(entity.skinID); var isTargetNade = IsNpcGrenade(target.skinID); if (isEntityNade && isTargetNade) { return true; } else if (isEntityNade && !isTargetNade) { if (target is BasePlayer) { var player = target as BasePlayer; if (config.options.sleeperSafe && player.IsSleeping()) { return true; } if (!config.options.attackOwner && entity.OwnerID == player.userID) { return true; } if (config.options.useFriends || config.options.useClans || config.options.useTeams) { if (entity.OwnerID != player.userID && IsFriend(entity.OwnerID, player.userID)) { return true; } } } if (target is NPCPlayer) { if (config.options.npcSafe) { return true; } } if (target is BaseNpc) { if (config.options.animalSafe) { return true; } } } else if (isTargetNade && !isEntityNade) { if (config.options.npcSafe && entity is NPCPlayer) { return true; } else if (config.options.animalSafe && entity is BaseNpc) { return true; } } return null; } private object OnTurretTarget(AutoTurret turret, BaseEntity target) { if (target != null) { if (config.options.turretSafe && IsNpcGrenade(target.skinID)) { return true; } } return null; } private object CanBradleyApcTarget(BradleyAPC bradley, BaseEntity target) { if (target != null) { if (config.options.bradleySafe && IsNpcGrenade(target.skinID)) { return false; } var player = target as BasePlayer; if (IsNpcGrenade(bradley.skinID)) { if (config.options.sleeperSafe && player.IsSleeping()) { return false; } if (!config.options.attackOwner && bradley.OwnerID == player.userID) { return false; } if (config.options.useFriends || config.options.useClans || config.options.useTeams) { if (bradley.OwnerID != player.userID && IsFriend(bradley.OwnerID, player.userID)) { return true; } } } } return null; } private object OnBradleyApcInitialize(BradleyAPC bradley) { if (!IsNpcGrenade(bradley.skinID)) { return null; } else { bradley._maxHealth = storedData.BradleyNPC[bradleyNade].Health; bradley.health = storedData.BradleyNPC[bradleyNade].Health; bradley.searchRange = storedData.BradleyNPC[bradleyNade].SearchRange; bradley.viewDistance = storedData.BradleyNPC[bradleyNade].ViewDistance; bradley.maxCratesToSpawn = storedData.BradleyNPC[bradleyNade].CratesToSpawn; bradley.throttle = storedData.BradleyNPC[bradleyNade].ThrottleResponse; bradley.leftThrottle = bradley.throttle; bradley.rightThrottle = bradley.throttle; bradley.ClearPath(); bradley.currentPath.Clear(); bradley.currentPathIndex = 0; bradley.DoAI = true; bradley.DoSimpleAI(); var position = bradley.transform.position; for (int i = 0; i < storedData.BradleyNPC[bradleyNade].PatrolPathNodes; i++) { position = position + UnityEngine.Random.onUnitSphere * storedData.BradleyNPC[bradleyNade].PatrolRange; position.y = TerrainMeta.HeightMap.GetHeight(position); bradley.currentPath.Add(position); } return true; } return null; } void OnEntitySpawned(NPCPlayerCorpse corpse) { corpse.lootPanelName = "generic_resizable"; Inv npcInv = new Inv(); timer.Once(0.2f, () => { if (corpse == null || corpse.IsDestroyed) { return; } ulong id = corpse.playerSteamID; if (!NPCInventories.ContainsKey(id)) return; corpse.containers[0].Clear(); npcInv = NPCInventories[id]; if (npcInv == null) return; corpse._playerName = npcInv.name; var key = npcInv.name + " Grenade"; if (storedData.HumanNPC[key].StripCorpseLoot) { corpse.blockBagDrop = true; corpse.containers[0].Clear(); timer.Once(5f, () => NPCInventories?.Remove(id)); } else { for (int i = 0; i < npcInv.inventory.Length; i++) { foreach (var item in npcInv.inventory[i]) { var giveItem = ItemManager.CreateByItemID(item.ID, item.amount, item.skinID); if (i != 0) { if (giveItem.info.category == ItemCategory.Weapon || giveItem.info.category == ItemCategory.Attire) continue; } if (!giveItem.MoveToContainer(corpse.containers[0], -1, true)) giveItem.Remove(); } } } ItemManager.DoRemoves(); }); } void OnEntitySpawned(DroppedItemContainer bag) { Inv npcInv = new Inv(); bag.inventory.capacity = 36; NextTick(() => { if (bag == null) return; ulong id = bag.playerSteamID; npcInv = NPCInventories[id]; if (npcInv == null) return; bag.inventory.Clear(); if (!NPCInventories.ContainsKey(id)) return; bag._playerName = npcInv.name; bag.lootPanelName = "generic_resizable"; for (int i = 0; i < npcInv.inventory.Length; i++) { foreach (var item in npcInv.inventory[i]) { var giveItem = ItemManager.CreateByItemID(item.ID, item.amount, item.skinID); if (i != 0) { if (giveItem.info.category == ItemCategory.Weapon || giveItem.info.category == ItemCategory.Attire) continue; } if (!giveItem.MoveToContainer(bag.inventory, -1, true, false, null, true)) giveItem.Remove(); } } timer.Once(5f, () => NPCInventories?.Remove(id)); }); } #endregion #region Main // Changed to a custom method due to OnExplosiveThrown not hooking explosives thrown by NPCs private void OnNpcNadeThrown(BaseEntity thrower, BaseEntity entity, Item npcNade) { var player = thrower as BasePlayer; var nadeName = npcNade.name; var nadeSkin = npcNade.skin; if (!NadeInfo[nadeSkin].Enabled) { NextTick(() => { entity.Kill(); }); if (player is NPCPlayer) { Puts($"An NPC player is trying to throw an NPCGrenade which is not enabled in the config: {nadeName}"); return; } else { GiveNade(player, nadeSkin, nadeName, 1, "refund"); Message(player, "NotEnabled", nadeName); return; } } else if (config.options.usePerms && !permission.UserHasPermission(player.UserIDString, NadeInfo[nadeSkin].Perm)) { if (player is BasePlayer) { NextTick(() => { entity.Kill(); }); GiveNade(player, nadeSkin, nadeName, 1, "refund"); Message(player, "Permission", nadeName); return; } } timer.Once(2.4f, () => { if (entity != null) { var position = entity.transform.position; NextTick(() => { entity.Kill(); }); if (player == null || !player.IsAlive()) { return; } if (storedData.HumanNPC.ContainsKey(nadeName)) { NPCPlayerData settings = new NPCPlayerData { Name = storedData.HumanNPC[nadeName].Name, Prefab = storedData.HumanNPC[nadeName].Prefab, Health = storedData.HumanNPC[nadeName].Health, MaxRoamRange = storedData.HumanNPC[nadeName].MaxRoamRange, SenseRange = storedData.HumanNPC[nadeName].SenseRange, ListenRange = storedData.HumanNPC[nadeName].ListenRange, AggroRange = storedData.HumanNPC[nadeName].AggroRange, DeAggroRange = storedData.HumanNPC[nadeName].DeAggroRange, TargetLostRange = storedData.HumanNPC[nadeName].TargetLostRange, MemoryDuration = storedData.HumanNPC[nadeName].MemoryDuration, VisionCone = storedData.HumanNPC[nadeName].VisionCone, CheckVisionCone = storedData.HumanNPC[nadeName].CheckVisionCone, CheckLOS = storedData.HumanNPC[nadeName].CheckLOS, IgnoreNonVisionSneakers = storedData.HumanNPC[nadeName].IgnoreNonVisionSneakers, DamageScale = storedData.HumanNPC[nadeName].DamageScale, PeaceKeeper = storedData.HumanNPC[nadeName].PeaceKeeper, IgnoreSafeZonePlayers = storedData.HumanNPC[nadeName].IgnoreSafeZonePlayers, RadioChatter = storedData.HumanNPC[nadeName].RadioChatter, DeathSound = storedData.HumanNPC[nadeName].DeathSound, NumberToSpawn = storedData.HumanNPC[nadeName].NumberToSpawn, SpawnRadius = storedData.HumanNPC[nadeName].SpawnRadius, DespawnTime = storedData.HumanNPC[nadeName].DespawnTime, KillInSafeZone = storedData.HumanNPC[nadeName].KillInSafeZone, StripCorpseLoot = storedData.HumanNPC[nadeName].StripCorpseLoot, Speed = storedData.HumanNPC[nadeName].Speed, Acceleration = storedData.HumanNPC[nadeName].Acceleration, FastSpeedFraction = storedData.HumanNPC[nadeName].FastSpeedFraction, NormalSpeedFraction = storedData.HumanNPC[nadeName].NormalSpeedFraction, SlowSpeedFraction = storedData.HumanNPC[nadeName].SlowSpeedFraction, SlowestSpeedFraction = storedData.HumanNPC[nadeName].SlowestSpeedFraction, LowHealthMaxSpeedFraction = storedData.HumanNPC[nadeName].LowHealthMaxSpeedFraction, TurnSpeed = storedData.HumanNPC[nadeName].TurnSpeed, ExplosionSound = storedData.HumanNPC[nadeName].ExplosionSound }; if (settings.Prefab.Contains("scarecrow")) { SpawnScarecrow(player, npcNade, position, settings); return; } else { SpawnScientist(player, npcNade, position, settings); return; } } else if (storedData.AnimalNPC.ContainsKey(nadeName)) { SpawnAnimal(player, npcNade, position); return; } else if (storedData.BradleyNPC.ContainsKey(nadeName)) { SpawnBradley(player, npcNade, position); return; } } }); } private void SpawnScientist(BasePlayer player, Item npcNade, Vector3 position, NPCPlayerData settings) { if (player == null || npcNade == null || position == null || settings == null) { return; } for (int i = 0; i < settings.NumberToSpawn; i++) { if (settings.NumberToSpawn > 1) { Vector2 rand; rand = UnityEngine.Random.insideUnitCircle * settings.SpawnRadius; position = position + new Vector3(rand.x, 0, rand.y); } var groundLevel = TerrainMeta.HeightMap.GetHeight(position); var heightDiff = position.y - groundLevel; if (heightDiff < 6.0) { position.y = groundLevel; } var npc = (ScientistNPC)GameManager.server.CreateEntity(settings.Prefab, position + new Vector3(0, 0.1f, 0), new Quaternion(), true); if (npc == null) { return; } DoExplosion(settings.ExplosionSound, position); npc.Spawn(); npc.skinID = npcNade.skin; npc.OwnerID = player.userID; npc.displayName = settings.Name; npc.damageScale = settings.DamageScale; npc.startHealth = settings.Health; npc.InitializeHealth(settings.Health, settings.Health); npc.EnablePlayerCollider(); var brain = npc.gameObject.AddComponent(); brain.Settings = settings; var move = npc.gameObject.AddComponent(); move.Settings = settings; move.HomeLoc = position; if (heightDiff >= 6.0) { AddChute(npc, position); move.isFalling = true; } timer.Once(0.2f, () => { if (npc == null || npc.IsDestroyed) { return; } else if (SpawnAborted(player, npc, npcNade, position)) { return; } GiveGrenadeNpcKit(npc, npcNade); if (settings.DespawnTime > 0) { DespawnNPC(npc, npc.userID, settings.DespawnTime); } GrenadeNPCData.Add(npc.userID, npc); }); } } private void SpawnScarecrow(BasePlayer player, Item npcNade, Vector3 position, NPCPlayerData settings) { if (player == null || npcNade == null || position == null || settings == null) { return; } for (int i = 0; i < settings.NumberToSpawn; i++) { if (settings.NumberToSpawn > 1) { Vector2 rand; rand = UnityEngine.Random.insideUnitCircle * settings.SpawnRadius; position = position + new Vector3(rand.x, 0, rand.y); } var groundLevel = TerrainMeta.HeightMap.GetHeight(position); var heightDiff = position.y - groundLevel; if (heightDiff < 6.0) { position.y = groundLevel; } var npc = (ScarecrowNPC)GameManager.server.CreateEntity(settings.Prefab, position + new Vector3(0, 0.1f, 0), new Quaternion(), true); if (npc == null) { return; } DoExplosion(settings.ExplosionSound, position); npc.Spawn(); npc.skinID = npcNade.skin; npc.OwnerID = player.userID; npc.displayName = settings.Name; npc.damageScale = settings.DamageScale; npc.startHealth = settings.Health; npc.InitializeHealth(settings.Health, settings.Health); npc.EnablePlayerCollider(); var brain = npc.gameObject.AddComponent(); brain.Settings = settings; var move = npc.gameObject.AddComponent(); move.HomeLoc = position; move.Settings = settings; if (heightDiff >= 6.0) { AddChute(npc, position); move.isFalling = true; } timer.Once(0.2f, () => { if (npc == null || npc.IsDestroyed) { Puts("NPC is Null or Destroyed"); return; } if (SpawnAborted(player, npc, npcNade, position)) { Puts("Spawn Aborted!"); return; } GiveGrenadeNpcKit(npc, npcNade); if (settings.DespawnTime > 0) { DespawnNPC(npc, npc.userID, settings.DespawnTime); } GrenadeNPCData.Add(npc.userID, npc); }); } } private void SpawnAnimal(BasePlayer player, Item npcNade, Vector3 position) { if (storedData.AnimalNPC.ContainsKey(npcNade.name)) { string npcPrefab = storedData.AnimalNPC[npcNade.name].Prefab; string npcName = storedData.AnimalNPC[npcNade.name].Name; int spawnAmount = storedData.AnimalNPC[npcNade.name].NumberToSpawn; string exploSound = storedData.AnimalNPC[npcNade.name].ExplosionSound; for (int i = 0; i < spawnAmount; i++) { if (spawnAmount > 1) { Vector2 rand; rand = UnityEngine.Random.insideUnitCircle * storedData.AnimalNPC[npcNade.name].SpawnRadius; position = position + new Vector3(rand.x, 0, rand.y); } var groundLevel = TerrainMeta.HeightMap.GetHeight(position); var heightDiff = position.y - groundLevel; if (heightDiff > 6.0) { position.y = groundLevel; } BaseNpc npc = (BaseNpc)GameManager.server.CreateEntity(npcPrefab, position + new Vector3(0, 0.1f, 0)); if (npc == null) { return; } DoExplosion(exploSound, position); npc.Spawn(); npc.OwnerID = player.userID; npc.skinID = npcNade.skin; npc.CurrentBehaviour = BaseNpc.Behaviour.Attack; npc.startHealth = storedData.AnimalNPC[npcNade.name].Health; npc.InitializeHealth(storedData.AnimalNPC[npcNade.name].Health, storedData.AnimalNPC[npcNade.name].Health); npc.SetFact(BaseNpc.Facts.CanTargetEnemies, 1, true, true); npc.SetFact(BaseNpc.Facts.IsAggro, 1, true, true); if (SpawnAborted(player, npc, npcNade, position)) { return; } float despawnTime = storedData.AnimalNPC[npcNade.name].DespawnTime; if (despawnTime > 0) { DespawnNPC(npc, npc.net.ID.Value, despawnTime); } BaseNpcData.Add(npc.net.ID.Value, npc); } } } private void SpawnBradley(BasePlayer player, Item npcNade, Vector3 position) { if (storedData.BradleyNPC.ContainsKey(npcNade.name)) { string npcPrefab = storedData.BradleyNPC[npcNade.name].Prefab; string npcName = storedData.BradleyNPC[npcNade.name].Name; int spawnAmount = storedData.BradleyNPC[npcNade.name].NumberToSpawn; string exploSound = storedData.BradleyNPC[npcNade.name].ExplosionSound; DoExplosion(exploSound, position); for (int i = 0; i < spawnAmount; i++) { if (spawnAmount > 1) { Vector2 rand; rand = UnityEngine.Random.insideUnitCircle * storedData.AnimalNPC[npcNade.name].SpawnRadius; position = position + new Vector3(rand.x, 0, rand.y); } var groundLevel = TerrainMeta.HeightMap.GetHeight(position); var heightDiff = position.y - groundLevel; position.y = groundLevel; BradleyAPC npc = (BradleyAPC)GameManager.server.CreateEntity(npcPrefab, position + new Vector3(0, 0.1f, 0)); npc.OwnerID = player.userID; npc.skinID = npcNade.skin; npc.Spawn(); if (SpawnAborted(player, npc, npcNade, position)) { return; } float despawnTime = storedData.BradleyNPC[npcNade.name].DespawnTime; if (despawnTime > 0) { DespawnNPC(npc, npc.net.ID.Value, despawnTime); } BradleyAPCData.Add(npc.net.ID.Value, npc); } } } #endregion #region Helpers private void DespawnNPC(BaseEntity npc, ulong botId, float despawnTime) { timer.Once(despawnTime, () => { if (npc != null && !npc.IsDestroyed) { NextTick(() => { npc.Kill(); RemoveNpcData(botId); }); return; } }); } void AddChute(BaseEntity npc, Vector3 position) { var playerRigidbody = npc.gameObject.GetComponent(); playerRigidbody.isKinematic = false; playerRigidbody.useGravity = true; playerRigidbody.drag = config.options.chuteDrag; npc.gameObject.layer = 0; var Chute = GameManager.server.CreateEntity(chutePrefab, npc.transform.position, Quaternion.Euler(0, 0, 0)); Chute.gameObject.Identity(); Chute.SetParent(npc); Chute.transform.localPosition += new Vector3(0, 1.4f, 0); Chute.Spawn(); Chute.enableSaving = false; } private void CancelHit(HitInfo info) { info.damageTypes = new DamageTypeList(); info.DidHit = false; info.DoHitEffects = false; } private object OnNpcKits(ulong npcUserID) // Prevents conflict with NPCKits. { return GrenadeNPCData.ContainsKey(npcUserID) ? true : (object)null; } private void GiveNade(BasePlayer player, ulong skinId, string nadeName, int nadeAmount, string reason) { if (player == null && skinId == null || nadeName == null || reason == null) { return; } Item npcNade = ItemManager.CreateByItemID(f1GrenadeId, nadeAmount, skinId); npcNade.name = nadeName; if (reason == "give") { player.inventory.GiveItem(npcNade); Message(player, "Receive", nadeAmount, nadeName); } else if (reason == "refund") { player.inventory.GiveItem(npcNade); } } public Item GiveInventoryItem(ItemContainer itemContainer, string shortName, int itemAmount, ulong skinId) { Item item = ItemManager.CreateByName(shortName, itemAmount, skinId); if (item == null) return null; if (!item.MoveToContainer(itemContainer)) { item.Remove(0f); return null; } return item; } private bool CheckPermission(BasePlayer player, Item npcNade) { if (player == null || npcNade == null) { return false; } var perm = NadeInfo[npcNade.skin].Perm; if (player is NPCPlayer) { // Allow NPCs spawned by plugins which arm with NPCGrenades to work regardless of perms return true; } else if (permission.UserHasPermission(player.UserIDString, perm)) { return true; } return false; } private bool IsNpcGrenade(ulong skinId) { if (skinId == null) return false; if(NadeInfo.ContainsKey(skinId)) { return true; } return false; } public Vector3 GetNavPoint(Vector3 position) { NavMeshHit hit; if (!NavMesh.SamplePosition(position, out hit, 5, -1)) { return position; } else if (Physics.RaycastAll(hit.position + new Vector3(0, 100, 0), Vector3.down, 99f, 1235288065).Any()) { return position; } else if (hit.position.y < TerrainMeta.WaterMap.GetHeight(hit.position)) { return position; } position = hit.position; return position; } private bool IsInSafeZone(Vector3 position) { int loop = Physics.OverlapSphereNonAlloc(position, 1f, Vis.colBuffer, 1 << 18, QueryTriggerInteraction.Collide); for (int i = 0; i < loop; i++) { Collider collider = Vis.colBuffer[i]; if (collider.GetComponent()) { return true; } } return false; } private bool IsOnStructure(Vector3 position) { RaycastHit hit; var heightOffset = new Vector3(0, 3.0f, 0); if (Physics.Raycast(position + heightOffset, Vector3.down, out hit, 10f, LayerMask.GetMask("Construction")) && hit.GetEntity().IsValid()) { if (hit.GetEntity().name.Contains("building")) { return true; } } return false; } private bool IsInRock(Vector3 position) { RaycastHit hit; string[] colliders = new string[] { "rock", "cliff", "junk", "range", "invisible" }; Physics.queriesHitBackfaces = true; if (Physics.Raycast(position, Vector3.up, out hit, 25f, 65536, QueryTriggerInteraction.Ignore)) { if (colliders.Any(x => hit.collider?.gameObject?.name.Contains(x, CompareOptions.OrdinalIgnoreCase) != null)) { return true; } } Physics.queriesHitBackfaces = false; return false; } private void DoExplosion(string sound, Vector3 position) { try { Effect.server.Run(sound, position); } catch { Puts($"Invalid explosion effect path specified."); } } private IPlayer FindPlayer(string nameOrIdOrIp) { foreach (var activePlayer in covalence.Players.Connected) { if (activePlayer.Id == nameOrIdOrIp) return activePlayer; if (activePlayer.Name.Contains(nameOrIdOrIp)) return activePlayer; if (activePlayer.Name.ToLower().Contains(nameOrIdOrIp.ToLower())) return activePlayer; if (activePlayer.Address == nameOrIdOrIp) return activePlayer; } return null; } private bool IsFriend(ulong playerId, ulong targetId) { if (playerId == 0 || targetId == 0) { return false; } if (playerId == targetId) { return true; } if (Clans) { var result = Clans?.Call("IsMemberOrAlly", playerId, targetId); if (result != null && Convert.ToBoolean(result)) { return true; } } if (Friends) { var result = Friends?.Call("AreFriends", playerId, targetId); if (result != null && Convert.ToBoolean(result)) { return true; } } RelationshipManager.PlayerTeam team; RelationshipManager.ServerInstance.playerToTeam.TryGetValue(playerId, out team); if (team == null) { return false; } if (team.members.Contains(targetId)) { return true; } return false; } private bool SpawnAborted(BasePlayer player, BaseEntity npc, Item npcNade, Vector3 position) { if (npc == null || npc.IsDestroyed || npcNade == null) { return true; } else if (player == null || !player.IsAlive()) { return true; } if (npc.WaterFactor() > 0.7f) { NextTick(() => { npc.Kill(); }); Message(player, "UnderWater", npcNade.name, npc.net.ID.Value); return true; } else if (IsInSafeZone(position)) { NextTick(() => { npc.Kill(); }); Message(player, "InSafeZone", npcNade.name, npc.net.ID.Value); return true; } else if (!npc.IsOutside()) { NextTick(() => { npc.Kill(); }); Message(player, "Inside", npcNade.name, npc.net.ID.Value); return true; } else if (IsOnStructure(position)) { NextTick(() => { npc.Kill(); }); Message(player, "OnStructure", npcNade.name, npc.net.ID.Value); return true; } else if (IsInRock(position)) { NextTick(() => { npc.Kill(); }); Message(player, "IsInRock", npcNade.name, npc.net.ID.Value); return true; } return false; } private void RemoveNpcData(ulong npcId) { if (GrenadeNPCData.ContainsKey(npcId)) { GrenadeNPCData.Remove(npcId); } else if (BaseNpcData.ContainsKey(npcId)) { BaseNpcData.Remove(npcId); } else if (BradleyAPCData.ContainsKey(npcId)) { BradleyAPCData.Remove(npcId); } } private void SaveData() { Interface.Oxide.DataFileSystem.WriteObject(Name, storedData); } private void GiveGrenadeNpcKit(NPCPlayer npc, Item npcNade) { if (storedData.HumanNPC.ContainsKey(npcNade.name)) { if (npc == null) return; int kitCount = storedData.HumanNPC[npcNade.name].KitList.Count(); if (Kits) { if (kitCount > 0) { var kit = storedData.HumanNPC[npcNade.name].KitList[random.Next(kitCount)]; object kitCheck = Kits?.CallHook("GetKitInfo", kit, true); if (kitCheck == null) { Puts($"Kit: \"{kit}\" does not exist, using default kit."); } else { npc.inventory.Strip(); Kits?.Call($"GiveKit", npc, kit, true); return; } } } npc.inventory.Strip(); foreach (var item in storedData.HumanNPC[npcNade.name].DefaultLoadout) { var container = item.Container.ToLower(); if (container == "belt") { GiveInventoryItem(npc.inventory.containerBelt, item.Shortname, item.Amount, item.SkinID); } else if (container == "wear") { GiveInventoryItem(npc.inventory.containerWear, item.Shortname, item.Amount, item.SkinID); } else { GiveInventoryItem(npc.inventory.containerMain, item.Shortname, item.Amount, item.SkinID); } } } } #endregion #region Commands [Command("npcnade.give")] private void CmdGiveNpcNade(IPlayer player, string command, string[] args) { if (!player.HasPermission(permAdmin)) { Message(player, "NotAdmin"); return; } else if (args?.Length < 2) { if (player.IsServer) { Message(player, "SyntaxConsole"); return; } else { Message(player, "SyntaxPlayer"); return; } } int nadeAmount = 1; if (args?.Length == 3) { int amt; if (Int32.TryParse(args[2], out amt)) { nadeAmount = amt; } else { if (player.IsServer) { Message(player, "SyntaxConsole"); return; } else { Message(player, "SyntaxPlayer"); return; } } } var target = FindPlayer(args[1])?.Object as BasePlayer; if (target == null) { Message(player, "PlayerNotFound", args[1]); return; } string npcCmd = args[0].ToLower(); ulong skinId = 0; string nadeName = string.Empty; bool isEnabled = false; foreach (var item in NadeInfo.Keys) { if (npcCmd == NadeInfo[item].Cmd) { skinId = NadeInfo[item].ID; nadeName = NadeInfo[item].Name; isEnabled = NadeInfo[item].Enabled; break; } } if (skinId == 0) { Message(player, "InvalidNade", npcCmd); return; } else if (nadeName != null && !isEnabled) { Message(player, "NotEnabled", nadeName); return; } GiveNade(target, skinId, nadeName, nadeAmount, "give"); Message(player, "PlayerReceive", target.displayName, target.userID, nadeAmount, nadeName); } #endregion #region Brain AI private class ScarecrowAI : FacepunchBehaviour { private ScarecrowNPC Scarecrow; public NPCPlayerData Settings; public bool isEquippingWeapon = false; public AttackEntity CurrentWeapon { get; private set; } private void Start() { Scarecrow = GetComponent(); Invoke(nameof(InitBrain), 0.25f); Invoke(nameof(EquipWeapon), 1f); InvokeRepeating(nameof(MeleeAttack), 1f, 1f); } private void InitBrain() { Scarecrow.Brain.TargetLostRange = Settings.TargetLostRange; Scarecrow.Brain.AllowedToSleep = false; Scarecrow.Brain.sleeping = false; Scarecrow.Brain.ForceSetAge(0); Scarecrow.Brain.SenseRange = Settings.SenseRange; Scarecrow.Brain.Senses.Init(Scarecrow, Scarecrow.Brain, Settings.MemoryDuration, Settings.AggroRange, Settings.DeAggroRange, Settings.VisionCone, Settings.CheckVisionCone, Settings.CheckLOS, Settings.IgnoreNonVisionSneakers, Settings.ListenRange, Settings.PeaceKeeper, Scarecrow.Brain.MaxGroupSize > 0, Settings.IgnoreSafeZonePlayers, EntityType.Player, true); } private void MeleeAttack() { BaseEntity target = Scarecrow.Brain.Senses.GetNearestPlayer(Settings.AggroRange); BaseMelee heldEntity = Scarecrow?.GetActiveItem()?.GetHeldEntity() as BaseMelee; if (target != null && Vector3.Distance(target.transform.position, Scarecrow.transform.position) < 1.5f) { Scarecrow.StartAttacking(target); Scarecrow.Brain.Navigator.SetFacingDirectionEntity(target); Vector3 serverPos = target.ServerPosition - Scarecrow.ServerPosition; Scarecrow.ServerRotation = Quaternion.LookRotation(serverPos.normalized); heldEntity.StartAttackCooldown(heldEntity.repeatDelay * 2f); Scarecrow.SignalBroadcast(BaseEntity.Signal.Attack, string.Empty, null); if (!(heldEntity is Chainsaw)) { if (heldEntity.swingEffect.isValid) { Effect.server.Run(heldEntity.swingEffect.resourcePath, heldEntity.transform.position, Vector3.forward, Scarecrow.net.connection, false); } plugin.timer.Once(0.4f, () => { if (Scarecrow == null) { return; } Vector3 position = Scarecrow.eyes.position; Vector3 direction = Scarecrow.eyes.BodyForward(); for (int i = 0; i < 2; ++i) { List list = Pool.GetList(); GamePhysics.TraceAll(new Ray(position - direction * (i == 0 ? 0.0f : 0.2f), direction), i == 0 ? 0.0f : heldEntity.attackRadius, list, heldEntity.effectiveRange + 0.2f, 1219701521, QueryTriggerInteraction.UseGlobal); bool flag = false; for (int j = 0; j < list.Count; ++j) { RaycastHit item = list[j]; BaseEntity entity = item.GetEntity(); if (entity != null && Scarecrow != null) { float single = 0.0f; foreach (Rust.DamageTypeEntry damageType in heldEntity.damageTypes) { single += damageType.amount; } entity.OnAttacked(new HitInfo(Scarecrow, entity, Rust.DamageType.Slash, single * Scarecrow.damageScale)); HitInfo hitInfo = Pool.Get(); hitInfo.HitEntity = entity; hitInfo.HitPositionWorld = item.point; hitInfo.HitNormalWorld = -direction; if (entity is BaseNpc || entity is NPCPlayer || entity is BasePlayer) { hitInfo.HitMaterial = StringPool.Get("Flesh"); } else { hitInfo.HitMaterial = StringPool.Get((item.GetCollider().sharedMaterial != null ? item.GetCollider().sharedMaterial.GetName() : "generic")); } string strikeEffectPath = heldEntity.GetStrikeEffectPath(heldEntity.name); if (strikeEffectPath == null) { Effect.server.ImpactEffect(hitInfo); } else { Effect.server.Run(strikeEffectPath, hitInfo.HitEntity, hitInfo.HitBone, hitInfo.HitPositionLocal, hitInfo.HitNormalLocal); Effect.server.Run(fleshBloodImpact, hitInfo.HitEntity, hitInfo.HitBone, hitInfo.HitPositionLocal, hitInfo.HitNormalLocal); } Pool.Free(ref hitInfo); flag = true; if (!(entity != null) || entity.ShouldBlockProjectiles()) { break; } } } Pool.FreeList(ref list); if (flag) { break; } } }); } else if (heldEntity is Chainsaw) { if (!(heldEntity as Chainsaw).EngineOn()) { (heldEntity as Chainsaw).ServerNPCStart(); } heldEntity.SetFlag(BaseEntity.Flags.Busy, true, false, true); heldEntity.SetFlag(BaseEntity.Flags.Reserved8, true, false, true); } } else if (target != null && Vector3.Distance(target.transform.position, Scarecrow.transform.position) > 2.0f) { if (heldEntity is Chainsaw) { if (!(heldEntity as Chainsaw).EngineOn()) { (heldEntity as Chainsaw).ServerNPCStart(); } heldEntity.SetFlag(BaseEntity.Flags.Busy, false, false, true); heldEntity.SetFlag(BaseEntity.Flags.Reserved8, false, false, true); } } } private void EquipWeapon() { if (!isEquippingWeapon) { StartCoroutine(EquippingWeapon()); } } private IEnumerator EquippingWeapon() { Item slot = null; if (Scarecrow.inventory.containerBelt != null) { isEquippingWeapon = true; if (slot == null) { for (int i = 0; i < Scarecrow.inventory.containerBelt.itemList.Count; i++) { Item item = Scarecrow.inventory.containerBelt.GetSlot(i); if (item != null && item.GetHeldEntity() is AttackEntity) { slot = item; break; } } } if (slot != null) { HeldEntity heldEntity = slot.GetHeldEntity() as HeldEntity; if (heldEntity != null) { if (heldEntity is AttackEntity) { (heldEntity as AttackEntity).TopUpAmmo(); } if (heldEntity is Chainsaw) { (heldEntity as Chainsaw).ServerNPCStart(); } } CurrentWeapon = heldEntity as AttackEntity; } isEquippingWeapon = false; yield return null; } } } private class ScientistAI : FacepunchBehaviour { private ScientistNPC Scientist; public NPCPlayerData Settings; public bool isEquippingWeapon = false; public AttackEntity CurrentWeapon { get; private set; } private void Start() { Scientist = GetComponent(); Invoke(nameof(InitBrain), 0.1f); Invoke(nameof(EquipWeapon), 1f); } private void InitBrain() { Scientist.Brain.AllowedToSleep = false; Scientist.Brain.sleeping = false; Scientist.Brain.ForceSetAge(0); Scientist.Brain.SenseRange = Settings.SenseRange; Scientist.Brain.TargetLostRange = Settings.TargetLostRange; Scientist.Brain.Senses.Init(Scientist, Scientist.Brain, Settings.MemoryDuration, Settings.AggroRange, Settings.DeAggroRange, Settings.VisionCone, Settings.CheckVisionCone, Settings.CheckLOS, Settings.IgnoreNonVisionSneakers, Settings.ListenRange, Settings.PeaceKeeper, false, Settings.IgnoreSafeZonePlayers, EntityType.Player, true); } private void EquipWeapon() { if (!isEquippingWeapon) { StartCoroutine(EquippingWeapon()); } } private IEnumerator EquippingWeapon() { Item slot = null; if (Scientist.inventory.containerBelt != null) { isEquippingWeapon = true; if (slot == null) { for (int i = 0; i < Scientist.inventory.containerBelt.itemList.Count; i++) { Item item = Scientist.inventory.containerBelt.GetSlot(i); if (item != null && item.GetHeldEntity() is AttackEntity) { slot = item; break; } } } if (slot != null) { HeldEntity heldEntity = slot.GetHeldEntity() as HeldEntity; if (heldEntity != null) { if (heldEntity is AttackEntity) { (heldEntity as AttackEntity).TopUpAmmo(); } if (heldEntity is Chainsaw) { (heldEntity as Chainsaw).ServerNPCStart(); } } CurrentWeapon = heldEntity as AttackEntity; } isEquippingWeapon = false; yield return null; } } } #endregion #region Scientist Movement private class ScientistMovement : MonoBehaviour { public ScientistNPC Scientist; public Vector3 HomeLoc; public Vector3 TargetPos; public Vector3 CheckPos; public NPCPlayerData Settings; public CapsuleCollider collider; public StateStatus status = StateStatus.Error; public bool returningHome = false; public bool isRoaming = true; public bool isFalling = false; private void Start() { Scientist = GetComponent(); Invoke(nameof(Init), 1f); if (!isFalling) { Scientist.Brain.Navigator.PlaceOnNavMesh(0.1f); InvokeRepeating(nameof(MoveScientist), 1f, 4f); InvokeRepeating(nameof(CheckPosition), 2f, 1f); } else if (isFalling) { collider = Scientist.GetComponent(); if (collider != null) { collider.isTrigger = true; Scientist.GetComponent().radius += 2f; } } } private void Init() { Scientist.NavAgent.enabled = true; Scientist.NavAgent.areaMask = 1; Scientist.NavAgent.agentTypeID = -1372625422; Scientist.NavAgent.autoTraverseOffMeshLink = true; Scientist.NavAgent.autoRepath = true; Scientist.NavAgent.baseOffset = -0.1f; Scientist.Brain.Navigator.CanUseNavMesh = true; Scientist.Brain.Navigator.DefaultArea = "Walkable"; Scientist.Brain.Navigator.CanUseCustomNav = true; Scientist.Brain.Navigator.MaxRoamDistanceFromHome = Settings.MaxRoamRange; Scientist.Brain.Navigator.BestRoamPointMaxDistance = Settings.MaxRoamRange; Scientist.Brain.Navigator.BestMovementPointMaxDistance = Settings.MaxRoamRange; Scientist.Brain.Navigator.Speed = Settings.Speed; Scientist.Brain.Navigator.Acceleration = Settings.Acceleration; Scientist.Brain.Navigator.FastSpeedFraction = Settings.FastSpeedFraction; Scientist.Brain.Navigator.NormalSpeedFraction = Settings.NormalSpeedFraction; Scientist.Brain.Navigator.SlowSpeedFraction = Settings.SlowSpeedFraction; Scientist.Brain.Navigator.SlowestSpeedFraction = Settings.SlowestSpeedFraction; Scientist.Brain.Navigator.LowHealthMaxSpeedFraction = Settings.LowHealthMaxSpeedFraction; Scientist.Brain.Navigator.TurnSpeed = Settings.TurnSpeed; Scientist.Brain.Navigator.Init(Scientist, Scientist.NavAgent); Scientist.Brain.Navigator.Destination = HomeLoc; Scientist.Brain.SwitchToState(AIState.Roam, 0); } private void OnTriggerEnter(Collider col) { if (!isFalling) { return; } NavMeshHit hit; if (NavMesh.SamplePosition(Scientist.transform.position, out hit, 10, -1)) { if (collider != null) { collider.isTrigger = false; collider.radius -= 2f; } var rb = Scientist.gameObject.GetComponent(); rb.isKinematic = true; rb.useGravity = false; Scientist.gameObject.layer = 17; Scientist.ServerPosition = hit.position; HomeLoc = hit.position; foreach (var child in Scientist.children.Where(child => child.name.Contains("parachute"))) { child.SetParent(null); plugin.NextTick(() => { child.Kill(); }); Scientist.Brain.Navigator.PlaceOnNavMesh(0.1f); InvokeRepeating(nameof(MoveScientist), 1f, 4f); InvokeRepeating(nameof(CheckPosition), 1f, 1f); break; } isFalling = false; } } private void CheckPosition() { if (Scientist == null || Scientist.IsDestroyed) { CancelInvoke(nameof(CheckPosition)); return; } CheckPos = Scientist.transform.position; if (Scientist.Brain.Navigator.Destination == HomeLoc) { var distanceToHome = Vector3.Distance(Scientist.transform.position, HomeLoc); if (distanceToHome < 2) { Scientist.Brain.Navigator.Stop(); Scientist.Brain.SwitchToState(AIState.Idle, 0); status = StateStatus.Finished; returningHome = false; isRoaming = true; return; } } else if (Scientist.Brain.Navigator.Destination == TargetPos) { var distanceToTarget = Vector3.Distance(Scientist.transform.position, TargetPos); if (distanceToTarget < 2) { Scientist.Brain.Navigator.Stop(); Scientist.Brain.SwitchToState(AIState.Idle, 0); status = StateStatus.Finished; return; } } } private void ClearSenses() { if (!Scientist.HasBrain) { return; } Scientist.Brain.Senses.Players.Clear(); Scientist.Brain.Senses.Memory.Players.Clear(); Scientist.Brain.Senses.Memory.Targets.Clear(); Scientist.Brain.Senses.Memory.Threats.Clear(); Scientist.Brain.Senses.Memory.LOS.Clear(); Scientist.Brain.Senses.Memory.All.Clear(); Scientist.Brain.Navigator.ClearFacingDirectionOverride(); Scientist.Brain.SwitchToState(AIState.Roam, 0); status = StateStatus.Error; } private void MoveScientist() { if (Scientist == null || Scientist.IsDestroyed) { CancelInvoke(nameof(MoveScientist)); return; } if (Scientist.WaterFactor() > 0.7f) { plugin.NextTick(() => { Scientist.Kill(); }); return; } if (Scientist.Brain.Senses.Memory.Targets.Count > 0) { for (var i = 0; i < Scientist.Brain.Senses.Memory.Targets.Count; i++) { var player = Scientist.Brain.Senses.Memory.Targets[i] as BasePlayer; if (player == null || !player.IsAlive()) { ClearSenses(); returningHome = true; isRoaming = false; return; } else if (Scientist.Distance(player.transform.position) > Settings.TargetLostRange) { ClearSenses(); returningHome = true; isRoaming = false; return; } else if (config.options.attackOwner && Scientist.OwnerID == player.userID) { Scientist.Brain.Navigator.SetFacingDirectionEntity(player); Scientist.Brain.SwitchToState(AIState.Attack, 0); return; } else if (config.options.useFriends || config.options.useClans || config.options.useTeams) { if (Scientist.OwnerID != player.userID && !plugin.IsFriend(Scientist.OwnerID, player.userID)) { Scientist.Brain.SwitchToState(AIState.Attack, 0); return; } } } } // Check NPC has: reached destination || is new spawn || is stuck. if (status == StateStatus.Finished || status == StateStatus.Error || Scientist.transform.position == CheckPos) { var distanceHome = Vector3.Distance(Scientist.transform.position, HomeLoc); if (!returningHome) { if (isRoaming && distanceHome > Settings.MaxRoamRange) { returningHome = true; isRoaming = false; return; } if (isRoaming && distanceHome < Settings.MaxRoamRange) { Vector3 rand = UnityEngine.Random.insideUnitCircle.normalized * Settings.MaxRoamRange; Vector3 newPos = plugin.GetNavPoint(Scientist.transform.position + new Vector3(rand.x, 0f, rand.y)); SetDest(newPos); return; } } else if (returningHome) { ClearSenses(); SetDest(HomeLoc); return; } } } private void SetDest(Vector3 position) { if (position == null) { position = Scientist.transform.position; } TargetPos = position; Scientist.Brain.Navigator.Destination = position; Scientist.Brain.SwitchToState(AIState.Roam, 0); Scientist.Brain.Navigator.SetDestination(position, BaseNavigator.NavigationSpeed.Normal, 0f, 0f); status = StateStatus.Running; } private void OnDestroy() { if (!Scientist.IsDestroyed) { plugin.NextTick(() => { Scientist.Kill(); }); } CancelInvoke(nameof(MoveScientist)); CancelInvoke(nameof(CheckPosition)); } } #endregion #region Scarecrow Movement private class ScarecrowMovement : MonoBehaviour { public ScarecrowNPC Scarecrow; public Vector3 HomeLoc; public Vector3 TargetPos; public Vector3 CheckPos; public NPCPlayerData Settings; public CapsuleCollider collider; public StateStatus status = StateStatus.Error; public bool returningHome = false; public bool isRoaming = true; public bool isFalling = false; private void Start() { Scarecrow = GetComponent(); Invoke(nameof(Init), 1f); if (!isFalling) { InvokeRepeating(nameof(MoveScarecrow), 1f, 4f); InvokeRepeating(nameof(CheckPosition), 1f, 1f); } else if (isFalling) { collider = Scarecrow.GetComponent(); if (collider != null) { collider.isTrigger = true; Scarecrow.GetComponent().radius += 2f; } } if (Settings.RadioChatter) { InvokeRepeating(nameof(BreathingChatter), 2f, 10f); } } private void Init() { Scarecrow.NavAgent.enabled = true; Scarecrow.NavAgent.areaMask = 1; Scarecrow.NavAgent.agentTypeID = -1372625422; Scarecrow.NavAgent.autoTraverseOffMeshLink = true; Scarecrow.NavAgent.autoRepath = true; Scarecrow.NavAgent.baseOffset = -0.1f; Scarecrow.Brain.Navigator.CanUseNavMesh = true; Scarecrow.Brain.Navigator.DefaultArea = "Walkable"; Scarecrow.Brain.Navigator.CanUseCustomNav = true; Scarecrow.Brain.Navigator.MaxRoamDistanceFromHome = Settings.MaxRoamRange; Scarecrow.Brain.Navigator.BestRoamPointMaxDistance = Settings.MaxRoamRange; Scarecrow.Brain.Navigator.BestMovementPointMaxDistance = Settings.MaxRoamRange; Scarecrow.Brain.Navigator.Speed = Settings.Speed; Scarecrow.Brain.Navigator.Acceleration = Settings.Acceleration; Scarecrow.Brain.Navigator.FastSpeedFraction = Settings.FastSpeedFraction; Scarecrow.Brain.Navigator.NormalSpeedFraction = Settings.NormalSpeedFraction; Scarecrow.Brain.Navigator.SlowSpeedFraction = Settings.SlowSpeedFraction; Scarecrow.Brain.Navigator.SlowestSpeedFraction = Settings.SlowestSpeedFraction; Scarecrow.Brain.Navigator.LowHealthMaxSpeedFraction = Settings.LowHealthMaxSpeedFraction; Scarecrow.Brain.Navigator.TurnSpeed = Settings.TurnSpeed; Scarecrow.Brain.Navigator.Destination = HomeLoc; Scarecrow.Brain.SwitchToState(AIState.Idle, 0); } private void OnTriggerEnter(Collider col) { if (!isFalling) { return; } NavMeshHit hit; if (NavMesh.SamplePosition(Scarecrow.transform.position, out hit, 10, -1)) { if (collider != null) { collider.isTrigger = false; collider.radius -= 2f; } var rb = Scarecrow.gameObject.GetComponent(); rb.isKinematic = true; rb.useGravity = false; Scarecrow.gameObject.layer = 17; Scarecrow.ServerPosition = hit.position; HomeLoc = hit.position; foreach (var child in Scarecrow.children.Where(child => child.name.Contains("parachute"))) { child.SetParent(null); plugin.NextTick(() => { child.Kill(); }); Scarecrow.Brain.Navigator.PlaceOnNavMesh(0.1f); Scarecrow.Brain.SwitchToState(AIState.Roam, 0); InvokeRepeating(nameof(MoveScarecrow), 2f, 4f); InvokeRepeating(nameof(CheckPosition), 1f, 1f); break; } isFalling = false; } } private void CheckPosition() { if (Scarecrow == null || Scarecrow.IsDestroyed) { CancelInvoke(nameof(CheckPosition)); return; } CheckPos = Scarecrow.transform.position; if (Scarecrow.Brain.Navigator.Destination == HomeLoc) { var distanceToHome = Vector3.Distance(Scarecrow.transform.position, HomeLoc); if (distanceToHome < 2) { Scarecrow.Brain.Navigator.Stop(); Scarecrow.Brain.SwitchToState(AIState.Idle, 0); status = StateStatus.Finished; returningHome = false; isRoaming = true; return; } } else if (Scarecrow.Brain.Navigator.Destination == TargetPos) { var distanceToTarget = Vector3.Distance(Scarecrow.transform.position, TargetPos); if (distanceToTarget < 2) { Scarecrow.Brain.Navigator.Stop(); Scarecrow.Brain.SwitchToState(AIState.Idle, 0); status = StateStatus.Finished; return; } } } private void ClearSenses() { if (!Scarecrow.HasBrain) { return; } Scarecrow.Brain.Senses.Players.Clear(); Scarecrow.Brain.Senses.Memory.Players.Clear(); Scarecrow.Brain.Senses.Memory.Targets.Clear(); Scarecrow.Brain.Senses.Memory.Threats.Clear(); Scarecrow.Brain.Senses.Memory.LOS.Clear(); Scarecrow.Brain.Senses.Memory.All.Clear(); Scarecrow.Brain.Navigator.ClearFacingDirectionOverride(); Scarecrow.Brain.SwitchToState(AIState.Roam, 0); status = StateStatus.Error; } private void MoveScarecrow() { if (Scarecrow == null || Scarecrow.IsDestroyed) { CancelInvoke(nameof(MoveScarecrow)); return; } if (Scarecrow.WaterFactor() > 0.7f) { plugin.NextTick(() => { Scarecrow.Kill(); }); return; } if (Scarecrow.Brain.Senses.Memory.Targets.Count > 0) { for (var i = 0; i < Scarecrow.Brain.Senses.Memory.Targets.Count; i++) { var player = Scarecrow.Brain.Senses.Memory.Targets[i] as BasePlayer; if (player == null || !player.IsAlive()) { ClearSenses(); returningHome = true; isRoaming = false; return; } else if (Scarecrow.Distance(player.transform.position) > Settings.TargetLostRange) { ClearSenses(); returningHome = true; isRoaming = false; return; } else if (config.options.attackOwner && Scarecrow.OwnerID == player.userID) { Scarecrow.Brain.SwitchToState(AIState.Attack, 0); return; } else if (config.options.useFriends || config.options.useClans || config.options.useTeams) { if (Scarecrow.OwnerID != player.userID && !plugin.IsFriend(Scarecrow.OwnerID, player.userID)) { Scarecrow.Brain.SwitchToState(AIState.Attack, 0); return; } } } } // Check NPC has: reached destination || is new spawn || is stuck. if (status == StateStatus.Finished || status == StateStatus.Error || Scarecrow.transform.position == CheckPos) { var distanceHome = Vector3.Distance(Scarecrow.transform.position, HomeLoc); if (!returningHome) { if (isRoaming && distanceHome > Settings.MaxRoamRange) { returningHome = true; isRoaming = false; return; } if (isRoaming && distanceHome < Settings.MaxRoamRange) { Vector3 rand = UnityEngine.Random.insideUnitCircle.normalized * Settings.MaxRoamRange; Vector3 newPos = plugin.GetNavPoint(Scarecrow.transform.position + new Vector3(rand.x, 0f, rand.y)); SetDest(newPos); return; } } else if (returningHome) { ClearSenses(); SetDest(HomeLoc); return; } } } private void SetDest(Vector3 position) { if (position == null) { position = Scarecrow.transform.position; } TargetPos = position; Scarecrow.Brain.Navigator.Destination = position; Scarecrow.Brain.SwitchToState(AIState.Roam, 0); Scarecrow.Brain.Navigator.SetDestination(position, BaseNavigator.NavigationSpeed.Normal, 0f, 0f); status = StateStatus.Running; } private void BreathingChatter() { if (Scarecrow == null || Scarecrow.IsDestroyed) { CancelInvoke(nameof(BreathingChatter)); return; } BaseEntity target = Scarecrow.Brain.Senses.GetNearestTarget(Settings.AggroRange); if (target != null && Scarecrow.IsAlive() && Vector3.Distance(Scarecrow.transform.position, target.transform.position) < Settings.AggroRange) { Effect.server.Run(murdererChatter, Scarecrow, StringPool.Get("head"), Vector3.zero, Vector3.zero, null, false); } } private void OnDestroy() { if (!Scarecrow.IsDestroyed) { plugin.NextTick(() => { Scarecrow.Kill(); }); } if (Settings.DeathSound) { Effect.server.Run(murdererDeath, Scarecrow.transform.position); } CancelInvoke(nameof(MoveScarecrow)); CancelInvoke(nameof(BreathingChatter)); CancelInvoke(nameof(CheckPosition)); } } #endregion #region Config private static ConfigData config; private class ConfigData { [JsonProperty(PropertyName = "General Options")] public Options options; [JsonProperty(PropertyName = "Human NPC Options")] public Human human; [JsonProperty(PropertyName = "Animal NPC Options")] public Animal animal; [JsonProperty(PropertyName = "Bradley APC Options")] public Bradley bradley; public class Options { [JsonProperty(PropertyName = "Attack Owner")] public bool attackOwner; [JsonProperty(PropertyName = "Use Friends")] public bool useFriends; [JsonProperty(PropertyName = "Use Clans")] public bool useClans; [JsonProperty(PropertyName = "Use Teams")] public bool useTeams; [JsonProperty(PropertyName = "Use Permissions")] public bool usePerms; [JsonProperty(PropertyName = "Chat Prefix")] public string chatPrefix; [JsonProperty(PropertyName = "Use Chat Prefix")] public bool usePrefix; [JsonProperty(PropertyName = "NPC Safe")] public bool npcSafe; [JsonProperty(PropertyName = "Bradley Safe")] public bool bradleySafe; [JsonProperty(PropertyName = "Turret Safe")] public bool turretSafe; [JsonProperty(PropertyName = "Animal Safe")] public bool animalSafe; [JsonProperty(PropertyName = "Sleeper Safe")] public bool sleeperSafe; [JsonProperty(PropertyName = "Parachute Drag (Lower = Faster. eg: 0.6)")] public float chuteDrag; } public class Human { [JsonProperty(PropertyName = "Scientist Enabled")] public bool npcScientist; [JsonProperty(PropertyName = "Heavy Scientist Enabled")] public bool npcHeavy; [JsonProperty(PropertyName = "Juggernaut Enabled")] public bool npcJuggernaut; [JsonProperty(PropertyName = "Tunnel Dweller Enabled")] public bool npcTunnel; [JsonProperty(PropertyName = "Underwater Dweller Enabled")] public bool npcUnderwater; [JsonProperty(PropertyName = "Murderer Enabled")] public bool npcMurderer; [JsonProperty(PropertyName = "Scarecrow Enabled")] public bool npcScarecrow; [JsonProperty(PropertyName = "Mummy Enabled")] public bool npcMummy; } public class Animal { [JsonProperty(PropertyName = "Bear Enabled")] public bool npcBear; [JsonProperty(PropertyName = "Polar Bear Enabled")] public bool npcPolarbear; [JsonProperty(PropertyName = "Wolf Enabled")] public bool npcWolf; [JsonProperty(PropertyName = "Boar Enabled")] public bool npcBoar; [JsonProperty(PropertyName = "Stag Enabled")] public bool npcStag; [JsonProperty(PropertyName = "Chicken Enabled")] public bool npcChicken; } public class Bradley { [JsonProperty(PropertyName = "Bradley APC Enabled")] public bool npcBradley; [JsonProperty(PropertyName = "Bradley Can Damage Player Bases")] public bool bradleyBaseDamage; } public VersionNumber Version { get; set; } } private ConfigData GetDefaultConfig() { return new ConfigData { options = new ConfigData.Options { attackOwner = false, useFriends = true, useClans = true, useTeams = true, usePerms = true, chatPrefix = "[NPC Grenades]: ", usePrefix = true, npcSafe = true, bradleySafe = true, turretSafe = true, animalSafe = true, sleeperSafe = true, chuteDrag = 0.6f }, human = new ConfigData.Human { npcScientist = true, npcHeavy = true, npcJuggernaut = true, npcTunnel = true, npcUnderwater = true, npcMurderer = true, npcScarecrow = true, npcMummy = true }, animal = new ConfigData.Animal { npcBear = true, npcPolarbear = true, npcWolf = true, npcBoar = true, npcStag = true, npcChicken = true }, bradley = new ConfigData.Bradley { npcBradley = true, bradleyBaseDamage = false }, Version = Version }; } protected override void LoadConfig() { base.LoadConfig(); try { config = Config.ReadObject(); if (config == null) { LoadDefaultConfig(); } else { UpdateConfigValues(); } } catch (Exception ex) { if (ex is JsonSerializationException || ex is NullReferenceException || ex is JsonReaderException) { Puts($"Exception Type: {ex.GetType()}"); LoadDefaultConfig(); return; } throw; } } protected override void LoadDefaultConfig() { Puts("Configuration file missing or corrupt, creating default config file."); config = GetDefaultConfig(); SaveConfig(); } protected override void SaveConfig() { Config.WriteObject(config); } private void UpdateConfigValues() { previousVersion = config.Version; ConfigData defaultConfig = GetDefaultConfig(); if (config.Version < Version) { Puts("Config update detected! Updating config file..."); if (config.Version < new VersionNumber(1, 1, 0)) { config.options.useFriends = defaultConfig.options.useFriends; config.options.useClans = defaultConfig.options.useClans; config.options.useTeams = defaultConfig.options.useTeams; config.options.usePerms = defaultConfig.options.usePerms; config.human.npcScientist = defaultConfig.human.npcScientist; config.human.npcHeavy = defaultConfig.human.npcHeavy; config.human.npcJuggernaut = defaultConfig.human.npcJuggernaut; config.human.npcMurderer = defaultConfig.human.npcMurderer; config.human.npcScarecrow = defaultConfig.human.npcScarecrow; config.human.npcMummy = defaultConfig.human.npcMummy; config.animal.npcBear = defaultConfig.animal.npcBear; config.animal.npcWolf = defaultConfig.animal.npcWolf; config.animal.npcBoar = defaultConfig.animal.npcBoar; config.animal.npcStag = defaultConfig.animal.npcStag; config.animal.npcChicken = defaultConfig.animal.npcChicken; config.bradley.npcBradley = defaultConfig.bradley.npcBradley; } if (config.Version < new VersionNumber(1, 1, 4)) { config.options.chatPrefix = defaultConfig.options.chatPrefix; config.options.usePrefix = defaultConfig.options.usePrefix; config.options.npcSafe = defaultConfig.options.npcSafe; config.options.bradleySafe = defaultConfig.options.bradleySafe; config.options.turretSafe = defaultConfig.options.turretSafe; config.options.animalSafe = defaultConfig.options.animalSafe; } if (config.Version < new VersionNumber(1, 1, 5)) { config.options.attackOwner = defaultConfig.options.attackOwner; config.options.sleeperSafe = defaultConfig.options.sleeperSafe; } if (config.Version < new VersionNumber(1, 1, 8)) { config.human.npcTunnel = defaultConfig.human.npcTunnel; config.human.npcUnderwater = defaultConfig.human.npcUnderwater; config.human.npcMurderer = defaultConfig.human.npcMurderer; config.human.npcMummy = defaultConfig.human.npcMummy; config.human.npcScarecrow = defaultConfig.human.npcScarecrow; } if (config.Version < new VersionNumber(1, 1, 12)) { config.human.npcMurderer = defaultConfig.human.npcMurderer; config.human.npcMummy = defaultConfig.human.npcMummy; config.human.npcScarecrow = defaultConfig.human.npcScarecrow; } if (config.Version < new VersionNumber(1, 1, 19)) { config.bradley.bradleyBaseDamage = defaultConfig.bradley.bradleyBaseDamage; } if (config.Version < new VersionNumber(1, 2, 1)) { config.animal.npcPolarbear = defaultConfig.animal.npcPolarbear; } if (config.Version < new VersionNumber(1, 2, 3)) { config.options.chuteDrag = 0.6f; } Puts("Config update complete!"); } config.Version = Version; SaveConfig(); } #endregion #region Temporary Data private Dictionary GrenadeNPCData = new Dictionary(); private Dictionary BaseNpcData = new Dictionary(); private Dictionary BradleyAPCData = new Dictionary(); private Dictionary NPCInventories = new Dictionary(); private Dictionary NadeInfo = new Dictionary(); private class Inv { public string name; public List[] inventory = { new List(), new List(), new List() }; } private class InvContents { public int ID; public int amount; public ulong skinID; } private class GrenadeData { public string Name; public ulong ID; public string Cmd; public bool Enabled; public string Perm; public string Prefab; } private void LoadNadeInfo() { NadeInfo.Add(scientistSkinID, new GrenadeData()); NadeInfo[scientistSkinID].Name = scientistNade; NadeInfo[scientistSkinID].ID = scientistSkinID; NadeInfo[scientistSkinID].Cmd = "scientist"; NadeInfo[scientistSkinID].Enabled = config.human.npcScientist; NadeInfo[scientistSkinID].Perm = permScientist; NadeInfo[scientistSkinID].Prefab = scientistPrefab; NadeInfo.Add(heavySkinID, new GrenadeData()); NadeInfo[heavySkinID].Name = heavyNade; NadeInfo[heavySkinID].ID = heavySkinID; NadeInfo[heavySkinID].Cmd = "heavy"; NadeInfo[heavySkinID].Enabled = config.human.npcHeavy; NadeInfo[heavySkinID].Perm = permHeavy; NadeInfo[heavySkinID].Prefab = heavyPrefab; NadeInfo.Add(juggernautSkinID, new GrenadeData()); NadeInfo[juggernautSkinID].Name = juggernautNade; NadeInfo[juggernautSkinID].ID = juggernautSkinID; NadeInfo[juggernautSkinID].Cmd = "juggernaut"; NadeInfo[juggernautSkinID].Enabled = config.human.npcJuggernaut; NadeInfo[juggernautSkinID].Perm = permJuggernaut; NadeInfo[juggernautSkinID].Prefab = heavyPrefab; NadeInfo.Add(tunnelSkinID, new GrenadeData()); NadeInfo[tunnelSkinID].Name = tunnelNade; NadeInfo[tunnelSkinID].ID = tunnelSkinID; NadeInfo[tunnelSkinID].Cmd = "tunnel"; NadeInfo[tunnelSkinID].Enabled = config.human.npcTunnel; NadeInfo[tunnelSkinID].Perm = permTunnel; NadeInfo[tunnelSkinID].Prefab = scientistPrefab; NadeInfo.Add(underwaterSkinID, new GrenadeData()); NadeInfo[underwaterSkinID].Name = underwaterNade; NadeInfo[underwaterSkinID].ID = underwaterSkinID; NadeInfo[underwaterSkinID].Cmd = "underwater"; NadeInfo[underwaterSkinID].Enabled = config.human.npcUnderwater; NadeInfo[underwaterSkinID].Perm = permUnderwater; NadeInfo[underwaterSkinID].Prefab = scientistPrefab; NadeInfo.Add(murdererSkinID, new GrenadeData()); NadeInfo[murdererSkinID].Name = murdererNade; NadeInfo[murdererSkinID].ID = murdererSkinID; NadeInfo[murdererSkinID].Cmd = "murderer"; NadeInfo[murdererSkinID].Enabled = config.human.npcMurderer; NadeInfo[murdererSkinID].Perm = permMurderer; NadeInfo[murdererSkinID].Prefab = scarecrowPrefab; NadeInfo.Add(scarecrowSkinID, new GrenadeData()); NadeInfo[scarecrowSkinID].Name = scarecrowNade; NadeInfo[scarecrowSkinID].ID = scarecrowSkinID; NadeInfo[scarecrowSkinID].Cmd = "scarecrow"; NadeInfo[scarecrowSkinID].Enabled = config.human.npcScarecrow; NadeInfo[scarecrowSkinID].Perm = permScarecrow; NadeInfo[scarecrowSkinID].Prefab = scarecrowPrefab; NadeInfo.Add(mummySkinID, new GrenadeData()); NadeInfo[mummySkinID].Name = mummyNade; NadeInfo[mummySkinID].ID = mummySkinID; NadeInfo[mummySkinID].Cmd = "mummy"; NadeInfo[mummySkinID].Enabled = config.human.npcMummy; NadeInfo[mummySkinID].Perm = permMummy; NadeInfo[mummySkinID].Prefab = scarecrowPrefab; NadeInfo.Add(bearSkinID, new GrenadeData()); NadeInfo[bearSkinID].Name = bearNade; NadeInfo[bearSkinID].ID = bearSkinID; NadeInfo[bearSkinID].Cmd = "bear"; NadeInfo[bearSkinID].Enabled = config.animal.npcBear; NadeInfo[bearSkinID].Perm = permBear; NadeInfo[bearSkinID].Prefab = bearPrefab; NadeInfo.Add(polarbearSkinID, new GrenadeData()); NadeInfo[polarbearSkinID].Name = polarbearNade; NadeInfo[polarbearSkinID].ID = polarbearSkinID; NadeInfo[polarbearSkinID].Cmd = "polarbear"; NadeInfo[polarbearSkinID].Enabled = config.animal.npcPolarbear; NadeInfo[polarbearSkinID].Perm = permPolarbear; NadeInfo[polarbearSkinID].Prefab = polarbearPrefab; NadeInfo.Add(wolfSkinID, new GrenadeData()); NadeInfo[wolfSkinID].Name = wolfNade; NadeInfo[wolfSkinID].ID = wolfSkinID; NadeInfo[wolfSkinID].Cmd = "wolf"; NadeInfo[wolfSkinID].Enabled = config.animal.npcWolf; NadeInfo[wolfSkinID].Perm = permWolf; NadeInfo[wolfSkinID].Prefab = wolfPrefab; NadeInfo.Add(boarSkinID, new GrenadeData()); NadeInfo[boarSkinID].Name = boarNade; NadeInfo[boarSkinID].ID = boarSkinID; NadeInfo[boarSkinID].Cmd = "boar"; NadeInfo[boarSkinID].Enabled = config.animal.npcBoar; NadeInfo[boarSkinID].Perm = permBoar; NadeInfo[boarSkinID].Prefab = boarPrefab; NadeInfo.Add(stagSkinID, new GrenadeData()); NadeInfo[stagSkinID].Name = stagNade; NadeInfo[stagSkinID].ID = stagSkinID; NadeInfo[stagSkinID].Cmd = "stag"; NadeInfo[stagSkinID].Enabled = config.animal.npcStag; NadeInfo[stagSkinID].Perm = permStag; NadeInfo[stagSkinID].Prefab = stagPrefab; NadeInfo.Add(chickenSkinID, new GrenadeData()); NadeInfo[chickenSkinID].Name = chickenNade; NadeInfo[chickenSkinID].ID = chickenSkinID; NadeInfo[chickenSkinID].Cmd = "chicken"; NadeInfo[chickenSkinID].Enabled = config.animal.npcChicken; NadeInfo[chickenSkinID].Perm = permChicken; NadeInfo[chickenSkinID].Prefab = chickenPrefab; NadeInfo.Add(bradleySkinID, new GrenadeData()); NadeInfo[bradleySkinID].Name = bradleyNade; NadeInfo[bradleySkinID].ID = bradleySkinID; NadeInfo[bradleySkinID].Cmd = "bradley"; NadeInfo[bradleySkinID].Enabled = config.bradley.npcBradley; NadeInfo[bradleySkinID].Perm = permBradley; NadeInfo[bradleySkinID].Prefab = bradleyPrefab; } #endregion #region Stored Data private StoredData storedData; private class StoredData { public Dictionary HumanNPC = new Dictionary(); public Dictionary AnimalNPC = new Dictionary(); public Dictionary BradleyNPC = new Dictionary(); } private class NPCPlayerData { public string Name; public string Prefab; public float Health; public float MaxRoamRange; public float SenseRange; public float ListenRange; public float AggroRange; public float DeAggroRange; public float TargetLostRange; public float MemoryDuration; public float VisionCone; public bool CheckVisionCone; public bool CheckLOS; public bool IgnoreNonVisionSneakers; public float DamageScale; public bool PeaceKeeper; public bool IgnoreSafeZonePlayers; public bool RadioChatter; public bool DeathSound; public int NumberToSpawn; public int SpawnRadius; public float DespawnTime; public bool KillInSafeZone; public bool StripCorpseLoot; public List KitList = new List(); public float Speed; public float Acceleration; public float FastSpeedFraction; public float NormalSpeedFraction; public float SlowSpeedFraction; public float SlowestSpeedFraction; public float LowHealthMaxSpeedFraction; public float TurnSpeed; public ulong GrenadeSkinID; public string ExplosionSound; public List DefaultLoadout = new List(); } private class AnimalData { public string Name; public string Prefab; public float Health; public bool KillInSafeZone; public float DespawnTime; public int NumberToSpawn; public int SpawnRadius; public ulong GrenadeSkinID; public string ExplosionSound; } private class APCData { public string Name; public string Prefab; public float Health; public float ViewDistance; public float SearchRange; public float PatrolRange; public int PatrolPathNodes; public float ThrottleResponse; public int CratesToSpawn; public bool KillInSafeZone; public float DespawnTime; public int NumberToSpawn; public int SpawnRadius; public ulong GrenadeSkinID; public string ExplosionSound; } private class Loadout { public string Shortname; public ulong SkinID; public int Amount; [JsonProperty(PropertyName = "Container Type (Belt, Main, Wear)")] public string Container; } private void UpdateStoredData() { if (previousVersion < new VersionNumber(1, 2, 1)) { Puts($"Updating new data values, all existing values will remain unchanged."); if (storedData.HumanNPC.ContainsKey(scientistNade)) { storedData.HumanNPC[scientistNade].DefaultLoadout = ScientistLoadout; } if (storedData.HumanNPC.ContainsKey(heavyNade)) { storedData.HumanNPC[heavyNade].DefaultLoadout = HeavyLoadout; } if (storedData.HumanNPC.ContainsKey(juggernautNade)) { storedData.HumanNPC[juggernautNade].DefaultLoadout = JuggernautLoadout; } if (storedData.HumanNPC.ContainsKey(tunnelNade)) { storedData.HumanNPC[tunnelNade].DefaultLoadout = TunnelLoadout; } if (storedData.HumanNPC.ContainsKey(underwaterNade)) { storedData.HumanNPC[underwaterNade].DefaultLoadout = UnderwaterLoadout; } if (storedData.HumanNPC.ContainsKey(murdererNade)) { storedData.HumanNPC[murdererNade].DefaultLoadout = MurdererLoadout; } if (storedData.HumanNPC.ContainsKey(scarecrowNade)) { storedData.HumanNPC[scarecrowNade].DefaultLoadout = ScarecrowLoadout; } if (storedData.HumanNPC.ContainsKey(mummyNade)) { storedData.HumanNPC[mummyNade].DefaultLoadout = MummyLoadout; } } } private void LoadData() { if(!storedData.HumanNPC.ContainsKey(scientistNade)) { Puts($"Data contains no entries for {scientistNade}, populating default values."); storedData.HumanNPC.Add(scientistNade, new NPCPlayerData()); storedData.HumanNPC[scientistNade].Name = "Scientist"; storedData.HumanNPC[scientistNade].Prefab = scientistPrefab; storedData.HumanNPC[scientistNade].Health = 150f; storedData.HumanNPC[scientistNade].MaxRoamRange = 30f; storedData.HumanNPC[scientistNade].SenseRange = 40f; storedData.HumanNPC[scientistNade].ListenRange = 30f; storedData.HumanNPC[scientistNade].AggroRange = 30f; storedData.HumanNPC[scientistNade].DeAggroRange = 40f; storedData.HumanNPC[scientistNade].TargetLostRange = 50f; storedData.HumanNPC[scientistNade].MemoryDuration = 10f; storedData.HumanNPC[scientistNade].VisionCone = 135f; storedData.HumanNPC[scientistNade].CheckVisionCone = true; storedData.HumanNPC[scientistNade].CheckLOS = true; storedData.HumanNPC[scientistNade].IgnoreNonVisionSneakers = true; storedData.HumanNPC[scientistNade].DamageScale = 1f; storedData.HumanNPC[scientistNade].PeaceKeeper = false; storedData.HumanNPC[scientistNade].IgnoreSafeZonePlayers = true; storedData.HumanNPC[scientistNade].RadioChatter = true; storedData.HumanNPC[scientistNade].DeathSound = true; storedData.HumanNPC[scientistNade].NumberToSpawn = 1; storedData.HumanNPC[scientistNade].SpawnRadius = 10; storedData.HumanNPC[scientistNade].DespawnTime = 300f; storedData.HumanNPC[scientistNade].KillInSafeZone = true; storedData.HumanNPC[scientistNade].StripCorpseLoot = false; storedData.HumanNPC[scientistNade].KitList = new List(); storedData.HumanNPC[scientistNade].Speed = 6.2f; storedData.HumanNPC[scientistNade].Acceleration = 12f; storedData.HumanNPC[scientistNade].FastSpeedFraction = 1f; storedData.HumanNPC[scientistNade].NormalSpeedFraction = 0.5f; storedData.HumanNPC[scientistNade].SlowSpeedFraction = 0.3f; storedData.HumanNPC[scientistNade].SlowestSpeedFraction = 0.1f; storedData.HumanNPC[scientistNade].LowHealthMaxSpeedFraction = 0.5f; storedData.HumanNPC[scientistNade].TurnSpeed = 120f; storedData.HumanNPC[scientistNade].GrenadeSkinID = scientistSkinID; storedData.HumanNPC[scientistNade].ExplosionSound = explosionEffect; storedData.HumanNPC[scientistNade].DefaultLoadout = ScientistLoadout; } if(!storedData.HumanNPC.ContainsKey(heavyNade)) { Puts($"Data contains no entries for {heavyNade}, populating default values."); storedData.HumanNPC.Add(heavyNade, new NPCPlayerData()); storedData.HumanNPC[heavyNade].Name = "Heavy Scientist"; storedData.HumanNPC[heavyNade].Prefab = heavyPrefab; storedData.HumanNPC[heavyNade].Health = 300f; storedData.HumanNPC[heavyNade].MaxRoamRange = 30f; storedData.HumanNPC[heavyNade].SenseRange = 40f; storedData.HumanNPC[heavyNade].ListenRange = 30f; storedData.HumanNPC[heavyNade].AggroRange = 30f; storedData.HumanNPC[heavyNade].DeAggroRange = 40f; storedData.HumanNPC[heavyNade].TargetLostRange = 50f; storedData.HumanNPC[heavyNade].MemoryDuration = 10f; storedData.HumanNPC[heavyNade].VisionCone = 135f; storedData.HumanNPC[heavyNade].CheckVisionCone = true; storedData.HumanNPC[heavyNade].CheckLOS = true; storedData.HumanNPC[heavyNade].IgnoreNonVisionSneakers = true; storedData.HumanNPC[heavyNade].DamageScale = 2f; storedData.HumanNPC[heavyNade].PeaceKeeper = false; storedData.HumanNPC[heavyNade].IgnoreSafeZonePlayers = true; storedData.HumanNPC[heavyNade].RadioChatter = true; storedData.HumanNPC[heavyNade].DeathSound = true; storedData.HumanNPC[heavyNade].NumberToSpawn = 1; storedData.HumanNPC[heavyNade].SpawnRadius = 10; storedData.HumanNPC[heavyNade].DespawnTime = 300f; storedData.HumanNPC[heavyNade].KillInSafeZone = true; storedData.HumanNPC[heavyNade].StripCorpseLoot = false; storedData.HumanNPC[heavyNade].KitList = new List(); storedData.HumanNPC[heavyNade].Speed = 6.2f; storedData.HumanNPC[heavyNade].Acceleration = 12f; storedData.HumanNPC[heavyNade].FastSpeedFraction = 1f; storedData.HumanNPC[heavyNade].NormalSpeedFraction = 0.5f; storedData.HumanNPC[heavyNade].SlowSpeedFraction = 0.3f; storedData.HumanNPC[heavyNade].SlowestSpeedFraction = 0.1f; storedData.HumanNPC[heavyNade].LowHealthMaxSpeedFraction = 0.5f; storedData.HumanNPC[heavyNade].TurnSpeed = 120f; storedData.HumanNPC[heavyNade].GrenadeSkinID = heavySkinID; storedData.HumanNPC[heavyNade].ExplosionSound = explosionEffect; storedData.HumanNPC[heavyNade].DefaultLoadout = HeavyLoadout; } if(!storedData.HumanNPC.ContainsKey(juggernautNade)) { Puts($"Data contains no entries for {juggernautNade}, populating default values."); storedData.HumanNPC.Add(juggernautNade, new NPCPlayerData()); storedData.HumanNPC[juggernautNade].Name = "Juggernaut"; storedData.HumanNPC[juggernautNade].Prefab = heavyPrefab; storedData.HumanNPC[juggernautNade].Health = 900f; storedData.HumanNPC[juggernautNade].MaxRoamRange = 40f; storedData.HumanNPC[juggernautNade].SenseRange = 60f; storedData.HumanNPC[juggernautNade].ListenRange = 50f; storedData.HumanNPC[juggernautNade].AggroRange = 50f; storedData.HumanNPC[juggernautNade].DeAggroRange = 60f; storedData.HumanNPC[juggernautNade].TargetLostRange = 70f; storedData.HumanNPC[juggernautNade].MemoryDuration = 10f; storedData.HumanNPC[juggernautNade].VisionCone = 180f; storedData.HumanNPC[juggernautNade].CheckVisionCone = true; storedData.HumanNPC[juggernautNade].CheckLOS = true; storedData.HumanNPC[juggernautNade].IgnoreNonVisionSneakers = true; storedData.HumanNPC[juggernautNade].DamageScale = 3f; storedData.HumanNPC[juggernautNade].PeaceKeeper = false; storedData.HumanNPC[juggernautNade].IgnoreSafeZonePlayers = true; storedData.HumanNPC[juggernautNade].RadioChatter = true; storedData.HumanNPC[juggernautNade].DeathSound = true; storedData.HumanNPC[juggernautNade].NumberToSpawn = 1; storedData.HumanNPC[juggernautNade].SpawnRadius = 10; storedData.HumanNPC[juggernautNade].DespawnTime = 300f; storedData.HumanNPC[juggernautNade].KillInSafeZone = true; storedData.HumanNPC[juggernautNade].StripCorpseLoot = false; storedData.HumanNPC[juggernautNade].KitList = new List(); storedData.HumanNPC[juggernautNade].Speed = 6.2f; storedData.HumanNPC[juggernautNade].Acceleration = 12f; storedData.HumanNPC[juggernautNade].FastSpeedFraction = 1f; storedData.HumanNPC[juggernautNade].NormalSpeedFraction = 0.5f; storedData.HumanNPC[juggernautNade].SlowSpeedFraction = 0.3f; storedData.HumanNPC[juggernautNade].SlowestSpeedFraction = 0.1f; storedData.HumanNPC[juggernautNade].LowHealthMaxSpeedFraction = 0.5f; storedData.HumanNPC[juggernautNade].TurnSpeed = 120f; storedData.HumanNPC[juggernautNade].GrenadeSkinID = juggernautSkinID; storedData.HumanNPC[juggernautNade].ExplosionSound = explosionEffect; storedData.HumanNPC[juggernautNade].DefaultLoadout = JuggernautLoadout; } if(!storedData.HumanNPC.ContainsKey(tunnelNade)) { Puts($"Data contains no entries for {tunnelNade}, populating default values."); storedData.HumanNPC.Add(tunnelNade, new NPCPlayerData()); storedData.HumanNPC[tunnelNade].Name = "Tunnel Dweller"; storedData.HumanNPC[tunnelNade].Prefab = scientistPrefab; storedData.HumanNPC[tunnelNade].Health = 150f; storedData.HumanNPC[tunnelNade].MaxRoamRange = 30f; storedData.HumanNPC[tunnelNade].SenseRange = 40f; storedData.HumanNPC[tunnelNade].ListenRange = 30f; storedData.HumanNPC[tunnelNade].AggroRange = 30f; storedData.HumanNPC[tunnelNade].DeAggroRange = 40f; storedData.HumanNPC[tunnelNade].TargetLostRange = 50f; storedData.HumanNPC[tunnelNade].MemoryDuration = 10f; storedData.HumanNPC[tunnelNade].VisionCone = 135f; storedData.HumanNPC[tunnelNade].CheckVisionCone = true; storedData.HumanNPC[tunnelNade].CheckLOS = true; storedData.HumanNPC[tunnelNade].IgnoreNonVisionSneakers = true; storedData.HumanNPC[tunnelNade].DamageScale = 1f; storedData.HumanNPC[tunnelNade].PeaceKeeper = false; storedData.HumanNPC[tunnelNade].IgnoreSafeZonePlayers = true; storedData.HumanNPC[tunnelNade].RadioChatter = false; storedData.HumanNPC[tunnelNade].DeathSound = false; storedData.HumanNPC[tunnelNade].NumberToSpawn = 1; storedData.HumanNPC[tunnelNade].SpawnRadius = 10; storedData.HumanNPC[tunnelNade].DespawnTime = 300f; storedData.HumanNPC[tunnelNade].KillInSafeZone = true; storedData.HumanNPC[tunnelNade].StripCorpseLoot = false; storedData.HumanNPC[tunnelNade].KitList = new List(); storedData.HumanNPC[tunnelNade].Speed = 6.2f; storedData.HumanNPC[tunnelNade].Acceleration = 12f; storedData.HumanNPC[tunnelNade].FastSpeedFraction = 1f; storedData.HumanNPC[tunnelNade].NormalSpeedFraction = 0.5f; storedData.HumanNPC[tunnelNade].SlowSpeedFraction = 0.3f; storedData.HumanNPC[tunnelNade].SlowestSpeedFraction = 0.1f; storedData.HumanNPC[tunnelNade].LowHealthMaxSpeedFraction = 0.5f; storedData.HumanNPC[tunnelNade].TurnSpeed = 120f; storedData.HumanNPC[tunnelNade].GrenadeSkinID = tunnelSkinID; storedData.HumanNPC[tunnelNade].ExplosionSound = explosionEffect; storedData.HumanNPC[tunnelNade].DefaultLoadout = TunnelLoadout; } if(!storedData.HumanNPC.ContainsKey(underwaterNade)) { Puts($"Data contains no entries for {underwaterNade}, populating default values."); storedData.HumanNPC.Add(underwaterNade, new NPCPlayerData()); storedData.HumanNPC[underwaterNade].Name = "Underwater Dweller"; storedData.HumanNPC[underwaterNade].Prefab = scientistPrefab; storedData.HumanNPC[underwaterNade].Health = 150f; storedData.HumanNPC[underwaterNade].MaxRoamRange = 30f; storedData.HumanNPC[underwaterNade].SenseRange = 40f; storedData.HumanNPC[underwaterNade].ListenRange = 30f; storedData.HumanNPC[underwaterNade].AggroRange = 30f; storedData.HumanNPC[underwaterNade].DeAggroRange = 40f; storedData.HumanNPC[underwaterNade].TargetLostRange = 50f; storedData.HumanNPC[underwaterNade].MemoryDuration = 10f; storedData.HumanNPC[underwaterNade].VisionCone = 135f; storedData.HumanNPC[underwaterNade].CheckVisionCone = true; storedData.HumanNPC[underwaterNade].CheckLOS = true; storedData.HumanNPC[underwaterNade].IgnoreNonVisionSneakers = true; storedData.HumanNPC[underwaterNade].DamageScale = 1f; storedData.HumanNPC[underwaterNade].PeaceKeeper = false; storedData.HumanNPC[underwaterNade].IgnoreSafeZonePlayers = true; storedData.HumanNPC[underwaterNade].RadioChatter = false; storedData.HumanNPC[underwaterNade].DeathSound = false; storedData.HumanNPC[underwaterNade].NumberToSpawn = 1; storedData.HumanNPC[underwaterNade].SpawnRadius = 10; storedData.HumanNPC[underwaterNade].DespawnTime = 300f; storedData.HumanNPC[underwaterNade].KillInSafeZone = true; storedData.HumanNPC[underwaterNade].StripCorpseLoot = false; storedData.HumanNPC[underwaterNade].KitList = new List(); storedData.HumanNPC[underwaterNade].Speed = 6.2f; storedData.HumanNPC[underwaterNade].Acceleration = 12f; storedData.HumanNPC[underwaterNade].FastSpeedFraction = 1f; storedData.HumanNPC[underwaterNade].NormalSpeedFraction = 0.5f; storedData.HumanNPC[underwaterNade].SlowSpeedFraction = 0.3f; storedData.HumanNPC[underwaterNade].SlowestSpeedFraction = 0.1f; storedData.HumanNPC[underwaterNade].LowHealthMaxSpeedFraction = 0.5f; storedData.HumanNPC[underwaterNade].TurnSpeed = 120f; storedData.HumanNPC[underwaterNade].GrenadeSkinID = underwaterSkinID; storedData.HumanNPC[underwaterNade].ExplosionSound = explosionEffect; storedData.HumanNPC[underwaterNade].DefaultLoadout = UnderwaterLoadout; } if(!storedData.HumanNPC.ContainsKey(murdererNade)) { Puts($"Data contains no entries for {murdererNade}, populating default values."); storedData.HumanNPC.Add(murdererNade, new NPCPlayerData()); storedData.HumanNPC[murdererNade].Name = "Murderer"; storedData.HumanNPC[murdererNade].Prefab = scarecrowPrefab; storedData.HumanNPC[murdererNade].Health = 200f; storedData.HumanNPC[murdererNade].MaxRoamRange = 30f; storedData.HumanNPC[murdererNade].SenseRange = 40f; storedData.HumanNPC[murdererNade].ListenRange = 30f; storedData.HumanNPC[murdererNade].AggroRange = 30f; storedData.HumanNPC[murdererNade].DeAggroRange = 40f; storedData.HumanNPC[murdererNade].TargetLostRange = 50f; storedData.HumanNPC[murdererNade].MemoryDuration = 10f; storedData.HumanNPC[murdererNade].VisionCone = 135f; storedData.HumanNPC[murdererNade].CheckVisionCone = true; storedData.HumanNPC[murdererNade].CheckLOS = true; storedData.HumanNPC[murdererNade].IgnoreNonVisionSneakers = true; storedData.HumanNPC[murdererNade].DamageScale = 1f; storedData.HumanNPC[murdererNade].PeaceKeeper = false; storedData.HumanNPC[murdererNade].IgnoreSafeZonePlayers = true; storedData.HumanNPC[murdererNade].RadioChatter = true; storedData.HumanNPC[murdererNade].DeathSound = true; storedData.HumanNPC[murdererNade].NumberToSpawn = 1; storedData.HumanNPC[murdererNade].SpawnRadius = 10; storedData.HumanNPC[murdererNade].DespawnTime = 300f; storedData.HumanNPC[murdererNade].KillInSafeZone = true; storedData.HumanNPC[murdererNade].StripCorpseLoot = false; storedData.HumanNPC[murdererNade].KitList = new List(); storedData.HumanNPC[murdererNade].Speed = 6.2f; storedData.HumanNPC[murdererNade].Acceleration = 12f; storedData.HumanNPC[murdererNade].FastSpeedFraction = 1f; storedData.HumanNPC[murdererNade].NormalSpeedFraction = 0.5f; storedData.HumanNPC[murdererNade].SlowSpeedFraction = 0.3f; storedData.HumanNPC[murdererNade].SlowestSpeedFraction = 0.1f; storedData.HumanNPC[murdererNade].LowHealthMaxSpeedFraction = 0.5f; storedData.HumanNPC[murdererNade].TurnSpeed = 120f; storedData.HumanNPC[murdererNade].GrenadeSkinID = murdererSkinID; storedData.HumanNPC[murdererNade].ExplosionSound = explosionEffect; storedData.HumanNPC[murdererNade].DefaultLoadout = MurdererLoadout; } if(!storedData.HumanNPC.ContainsKey(scarecrowNade)) { Puts($"Data contains no entries for {scarecrowNade}, populating default values."); storedData.HumanNPC.Add(scarecrowNade, new NPCPlayerData()); storedData.HumanNPC[scarecrowNade].Name = "Scarecrow"; storedData.HumanNPC[scarecrowNade].Prefab = scarecrowPrefab; storedData.HumanNPC[scarecrowNade].Health = 200f; storedData.HumanNPC[scarecrowNade].MaxRoamRange = 30f; storedData.HumanNPC[scarecrowNade].SenseRange = 40f; storedData.HumanNPC[scarecrowNade].ListenRange = 30f; storedData.HumanNPC[scarecrowNade].AggroRange = 30f; storedData.HumanNPC[scarecrowNade].DeAggroRange = 40f; storedData.HumanNPC[scarecrowNade].TargetLostRange = 50f; storedData.HumanNPC[scarecrowNade].MemoryDuration = 10f; storedData.HumanNPC[scarecrowNade].VisionCone = 135f; storedData.HumanNPC[scarecrowNade].CheckVisionCone = true; storedData.HumanNPC[scarecrowNade].CheckLOS = true; storedData.HumanNPC[scarecrowNade].IgnoreNonVisionSneakers = true; storedData.HumanNPC[scarecrowNade].DamageScale = 1f; storedData.HumanNPC[scarecrowNade].PeaceKeeper = false; storedData.HumanNPC[scarecrowNade].IgnoreSafeZonePlayers = true; storedData.HumanNPC[scarecrowNade].RadioChatter = true; storedData.HumanNPC[scarecrowNade].DeathSound = true; storedData.HumanNPC[scarecrowNade].NumberToSpawn = 1; storedData.HumanNPC[scarecrowNade].SpawnRadius = 10; storedData.HumanNPC[scarecrowNade].DespawnTime = 300f; storedData.HumanNPC[scarecrowNade].KillInSafeZone = true; storedData.HumanNPC[scarecrowNade].StripCorpseLoot = false; storedData.HumanNPC[scarecrowNade].KitList = new List(); storedData.HumanNPC[scarecrowNade].Speed = 6.2f; storedData.HumanNPC[scarecrowNade].Acceleration = 12f; storedData.HumanNPC[scarecrowNade].FastSpeedFraction = 1f; storedData.HumanNPC[scarecrowNade].NormalSpeedFraction = 0.5f; storedData.HumanNPC[scarecrowNade].SlowSpeedFraction = 0.3f; storedData.HumanNPC[scarecrowNade].SlowestSpeedFraction = 0.1f; storedData.HumanNPC[scarecrowNade].LowHealthMaxSpeedFraction = 0.5f; storedData.HumanNPC[scarecrowNade].TurnSpeed = 120f; storedData.HumanNPC[scarecrowNade].GrenadeSkinID = scarecrowSkinID; storedData.HumanNPC[scarecrowNade].ExplosionSound = explosionEffect; storedData.HumanNPC[scarecrowNade].DefaultLoadout = ScarecrowLoadout; } if(!storedData.HumanNPC.ContainsKey(mummyNade)) { Puts($"Data contains no entries for {mummyNade}, populating default values."); storedData.HumanNPC.Add(mummyNade, new NPCPlayerData()); storedData.HumanNPC[mummyNade].Name = "Mummy"; storedData.HumanNPC[mummyNade].Prefab = scarecrowPrefab; storedData.HumanNPC[mummyNade].Health = 200f; storedData.HumanNPC[mummyNade].MaxRoamRange = 30f; storedData.HumanNPC[mummyNade].SenseRange = 40f; storedData.HumanNPC[mummyNade].ListenRange = 30f; storedData.HumanNPC[mummyNade].AggroRange = 30f; storedData.HumanNPC[mummyNade].DeAggroRange = 40f; storedData.HumanNPC[mummyNade].TargetLostRange = 50f; storedData.HumanNPC[mummyNade].MemoryDuration = 10f; storedData.HumanNPC[mummyNade].VisionCone = 135f; storedData.HumanNPC[mummyNade].CheckVisionCone = true; storedData.HumanNPC[mummyNade].CheckLOS = true; storedData.HumanNPC[mummyNade].IgnoreNonVisionSneakers = true; storedData.HumanNPC[mummyNade].DamageScale = 1f; storedData.HumanNPC[mummyNade].PeaceKeeper = false; storedData.HumanNPC[mummyNade].IgnoreSafeZonePlayers = true; storedData.HumanNPC[mummyNade].RadioChatter = true; storedData.HumanNPC[mummyNade].DeathSound = true; storedData.HumanNPC[mummyNade].NumberToSpawn = 1; storedData.HumanNPC[mummyNade].SpawnRadius = 10; storedData.HumanNPC[mummyNade].DespawnTime = 300f; storedData.HumanNPC[mummyNade].KillInSafeZone = true; storedData.HumanNPC[mummyNade].StripCorpseLoot = false; storedData.HumanNPC[mummyNade].KitList = new List(); storedData.HumanNPC[mummyNade].Speed = 6.2f; storedData.HumanNPC[mummyNade].Acceleration = 12f; storedData.HumanNPC[mummyNade].FastSpeedFraction = 1f; storedData.HumanNPC[mummyNade].NormalSpeedFraction = 0.5f; storedData.HumanNPC[mummyNade].SlowSpeedFraction = 0.3f; storedData.HumanNPC[mummyNade].SlowestSpeedFraction = 0.1f; storedData.HumanNPC[mummyNade].LowHealthMaxSpeedFraction = 0.5f; storedData.HumanNPC[mummyNade].TurnSpeed = 120f; storedData.HumanNPC[mummyNade].GrenadeSkinID = mummySkinID; storedData.HumanNPC[mummyNade].ExplosionSound = explosionEffect; storedData.HumanNPC[mummyNade].DefaultLoadout = MummyLoadout; } /////////////////////// BaseNPC //////////////////////// if(!storedData.AnimalNPC.ContainsKey(bearNade)) { Puts($"Data contains no entries for {bearNade}, populating default values."); storedData.AnimalNPC.Add(bearNade, new AnimalData()); storedData.AnimalNPC[bearNade].Name = "Bear"; storedData.AnimalNPC[bearNade].Prefab = bearPrefab; storedData.AnimalNPC[bearNade].Health = 400f; storedData.AnimalNPC[bearNade].KillInSafeZone = true; storedData.AnimalNPC[bearNade].DespawnTime = 300f; storedData.AnimalNPC[bearNade].NumberToSpawn = 1; storedData.AnimalNPC[bearNade].SpawnRadius = 10; storedData.AnimalNPC[bearNade].GrenadeSkinID = bearSkinID; storedData.AnimalNPC[bearNade].ExplosionSound = explosionEffect; } if(!storedData.AnimalNPC.ContainsKey(polarbearNade)) { Puts($"Data contains no entries for {polarbearNade}, populating default values."); storedData.AnimalNPC.Add(polarbearNade, new AnimalData()); storedData.AnimalNPC[polarbearNade].Name = "Polar Bear"; storedData.AnimalNPC[polarbearNade].Prefab = polarbearPrefab; storedData.AnimalNPC[polarbearNade].Health = 400f; storedData.AnimalNPC[polarbearNade].KillInSafeZone = true; storedData.AnimalNPC[polarbearNade].DespawnTime = 300f; storedData.AnimalNPC[polarbearNade].NumberToSpawn = 1; storedData.AnimalNPC[polarbearNade].SpawnRadius = 10; storedData.AnimalNPC[polarbearNade].GrenadeSkinID = polarbearSkinID; storedData.AnimalNPC[polarbearNade].ExplosionSound = explosionEffect; } if(!storedData.AnimalNPC.ContainsKey(wolfNade)) { Puts($"Data contains no entries for {wolfNade}, populating default values."); storedData.AnimalNPC.Add(wolfNade, new AnimalData()); storedData.AnimalNPC[wolfNade].Name = "Wolf"; storedData.AnimalNPC[wolfNade].Prefab = wolfPrefab; storedData.AnimalNPC[wolfNade].Health = 150f; storedData.AnimalNPC[wolfNade].KillInSafeZone = true; storedData.AnimalNPC[wolfNade].DespawnTime = 300f; storedData.AnimalNPC[wolfNade].NumberToSpawn = 1; storedData.AnimalNPC[wolfNade].SpawnRadius = 10; storedData.AnimalNPC[wolfNade].GrenadeSkinID = wolfSkinID; storedData.AnimalNPC[wolfNade].ExplosionSound = explosionEffect; } if(!storedData.AnimalNPC.ContainsKey(boarNade)) { Puts($"Data contains no entries for {boarNade}, populating default values."); storedData.AnimalNPC.Add(boarNade, new AnimalData()); storedData.AnimalNPC[boarNade].Name = "Boar"; storedData.AnimalNPC[boarNade].Prefab = boarPrefab; storedData.AnimalNPC[boarNade].Health = 150f; storedData.AnimalNPC[boarNade].KillInSafeZone = true; storedData.AnimalNPC[boarNade].DespawnTime = 300f; storedData.AnimalNPC[boarNade].NumberToSpawn = 1; storedData.AnimalNPC[boarNade].SpawnRadius = 10; storedData.AnimalNPC[boarNade].GrenadeSkinID = boarSkinID; storedData.AnimalNPC[boarNade].ExplosionSound = explosionEffect; } if(!storedData.AnimalNPC.ContainsKey(stagNade)) { Puts($"Data contains no entries for {stagNade}, populating default values."); storedData.AnimalNPC.Add(stagNade, new AnimalData()); storedData.AnimalNPC[stagNade].Name = "Stag"; storedData.AnimalNPC[stagNade].Prefab = stagPrefab; storedData.AnimalNPC[stagNade].Health = 150f; storedData.AnimalNPC[stagNade].KillInSafeZone = true; storedData.AnimalNPC[stagNade].DespawnTime = 300f; storedData.AnimalNPC[stagNade].NumberToSpawn = 1; storedData.AnimalNPC[stagNade].SpawnRadius = 10; storedData.AnimalNPC[stagNade].GrenadeSkinID = stagSkinID; storedData.AnimalNPC[stagNade].ExplosionSound = explosionEffect; } if(!storedData.AnimalNPC.ContainsKey(chickenNade)) { Puts($"Data contains no entries for {chickenNade}, populating default values."); storedData.AnimalNPC.Add(chickenNade, new AnimalData()); storedData.AnimalNPC[chickenNade].Name = "Chicken"; storedData.AnimalNPC[chickenNade].Prefab = chickenPrefab; storedData.AnimalNPC[chickenNade].Health = 25f; storedData.AnimalNPC[chickenNade].KillInSafeZone = true; storedData.AnimalNPC[chickenNade].DespawnTime = 300f; storedData.AnimalNPC[chickenNade].NumberToSpawn = 1; storedData.AnimalNPC[chickenNade].SpawnRadius = 10; storedData.AnimalNPC[chickenNade].GrenadeSkinID = chickenSkinID; storedData.AnimalNPC[chickenNade].ExplosionSound = explosionEffect; } //////////////////////// BradleyAPC ///////////////////////// if(!storedData.BradleyNPC.ContainsKey(bradleyNade)) { Puts($"Data contains no entries for {bradleyNade}, populating default values."); storedData.BradleyNPC.Add(bradleyNade, new APCData()); storedData.BradleyNPC[bradleyNade].Name = "Bradley APC"; storedData.BradleyNPC[bradleyNade].Prefab = bradleyPrefab; storedData.BradleyNPC[bradleyNade].Health = 1000f; storedData.BradleyNPC[bradleyNade].ViewDistance = 60f; storedData.BradleyNPC[bradleyNade].SearchRange = 40f; storedData.BradleyNPC[bradleyNade].PatrolRange = 20f; storedData.BradleyNPC[bradleyNade].PatrolPathNodes = 6; storedData.BradleyNPC[bradleyNade].ThrottleResponse = 1f; storedData.BradleyNPC[bradleyNade].CratesToSpawn = 3; storedData.BradleyNPC[bradleyNade].KillInSafeZone = true; storedData.BradleyNPC[bradleyNade].DespawnTime = 300f; storedData.BradleyNPC[bradleyNade].NumberToSpawn = 1; storedData.BradleyNPC[bradleyNade].SpawnRadius = 20; storedData.BradleyNPC[bradleyNade].GrenadeSkinID = bradleySkinID; storedData.BradleyNPC[bradleyNade].ExplosionSound = bradleyExplosion; } UpdateStoredData(); SaveData(); } #endregion #region Default Loadouts private static List ScientistLoadout { get { return new List { new Loadout { Shortname = "hazmatsuit_scientist", SkinID = 0, Amount = 1, Container = "Wear" }, new Loadout { Shortname = "rifle.ak", SkinID = 0, Amount = 1, Container = "Belt" }, new Loadout { Shortname = "ammo.rifle", SkinID = 0, Amount = 30, Container = "Main"} }; } } private static List HeavyLoadout { get { return new List { new Loadout { Shortname = "scientistsuit_heavy", SkinID = 0, Amount = 1, Container = "Wear" }, new Loadout { Shortname = "lmg.m249", SkinID = 0, Amount = 1, Container = "Belt" }, new Loadout { Shortname = "ammo.rifle", SkinID = 0, Amount = 50, Container = "Main"} }; } } private static List JuggernautLoadout { get { return new List { new Loadout { Shortname = "heavy.plate.helmet", SkinID = 0, Amount = 1, Container = "Wear" }, new Loadout { Shortname = "heavy.plate.jacket", SkinID = 0, Amount = 1, Container = "Wear" }, new Loadout { Shortname = "heavy.plate.pants", SkinID = 0, Amount = 1, Container = "Wear" }, new Loadout { Shortname = "shoes.boots", SkinID = 0, Amount = 1, Container = "Wear" }, new Loadout { Shortname = "lmg.m249", SkinID = 0, Amount = 1, Container = "Belt" }, new Loadout { Shortname = "ammo.rifle", SkinID = 0, Amount = 100, Container = "Main"} }; } } private static List TunnelLoadout { get { return new List { new Loadout { Shortname = "hat.gas.mask", SkinID = 0, Amount = 1, Container = "Wear" }, new Loadout { Shortname = "jumpsuit.suit", SkinID = 0, Amount = 1, Container = "Wear" }, new Loadout { Shortname = "shoes.boots", SkinID = 0, Amount = 1, Container = "Wear" }, new Loadout { Shortname = "pistol.m92", SkinID = 0, Amount = 1, Container = "Belt" }, new Loadout { Shortname = "ammo.pistol", SkinID = 0, Amount = 20, Container = "Main"} }; } } private static List UnderwaterLoadout { get { return new List { new Loadout { Shortname = "hat.gas.mask", SkinID = 0, Amount = 1, Container = "Wear" }, new Loadout { Shortname = "jumpsuit.suit.blue", SkinID = 0, Amount = 1, Container = "Wear" }, new Loadout { Shortname = "shoes.boots", SkinID = 0, Amount = 1, Container = "Wear" }, new Loadout { Shortname = "pistol.m92", SkinID = 0, Amount = 1, Container = "Belt" }, new Loadout { Shortname = "ammo.pistol", SkinID = 0, Amount = 20, Container = "Main"} }; } } private static List MurdererLoadout { get { return new List { new Loadout { Shortname = "burlap.headwrap", SkinID = 807624505, Amount = 1, Container = "Wear" }, new Loadout { Shortname = "gloweyes", SkinID = 0, Amount = 1, Container = "Wear" }, new Loadout { Shortname = "tshirt", SkinID = 795997221, Amount = 1, Container = "Wear" }, new Loadout { Shortname = "burlap.gloves", SkinID = 1132774091, Amount = 1, Container = "Wear" }, new Loadout { Shortname = "burlap.trousers", SkinID = 806966575, Amount = 1, Container = "Wear" }, new Loadout { Shortname = "shoes.boots", SkinID = 0, Amount = 1, Container = "Wear" }, new Loadout { Shortname = "machete", SkinID = 0, Amount = 1, Container = "Belt" } }; } } private static List ScarecrowLoadout { get { return new List { new Loadout { Shortname = "scarecrow.suit", SkinID = 0, Amount = 1, Container = "Wear" }, new Loadout { Shortname = "chainsaw", SkinID = 0, Amount = 1, Container = "Belt" }, new Loadout { Shortname = "lowgradefuel", SkinID = 0, Amount = 30, Container = "Main"} }; } } private static List MummyLoadout { get { return new List { new Loadout { Shortname = "halloween.mummysuit", SkinID = 0, Amount = 1, Container = "Wear" }, new Loadout { Shortname = "sickle", SkinID = 0, Amount = 1, Container = "Belt" } }; } } #endregion } }