C#:
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.8")]
[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<string, string>
{
["SyntaxPlayer"] = "Invalid syntax, use: /npcnade.give <type> <SteamID/PlayerName> <amount>",
["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 <type> <SteamID/PlayerName> <amount>",
["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<StoredData>(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 == 0)
{
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 == 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;
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;
}
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(() =>
{
if (entity != null)
entity.Kill();
});
if (player is NPCPlayer)
{
Puts($"An NPC player is trying to throw an NPCGrenade which is not enabled in the config: {nadeName}");
}
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(() =>
{
if (entity != null)
entity.Kill();
});
GiveNade(player, nadeSkin, nadeName, 1, "refund");
Message(player, "Permission", nadeName);
return;
}
}
timer.Once(2.4f, () =>
{
if (entity == null)
return;
var position = entity.transform.position;
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, nadeName, nadeSkin, position, settings);
}
else
{
SpawnScientist(player, nadeName, nadeSkin, position, settings);
}
}
if (storedData.AnimalNPC.ContainsKey(nadeName))
{
SpawnAnimal(player, nadeName, nadeSkin, position);
}
if (storedData.BradleyNPC.ContainsKey(nadeName))
{
SpawnBradley(player, nadeName, nadeSkin, position);
}
NextTick(() =>
{
if (entity != null)
entity.Kill();
});
});
}
private void SpawnScientist(BasePlayer player, string nadeName, ulong nadeSkin, Vector3 position, NPCPlayerData settings)
{
if (player == null || nadeName == null || nadeSkin == 0 || 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 = nadeSkin;
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<ScientistAI>();
brain.Settings = settings;
var move = npc.gameObject.AddComponent<ScientistMovement>();
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, nadeName, position))
{
return;
}
GiveGrenadeNpcKit(npc, nadeName);
if (settings.DespawnTime > 0)
{
DespawnNPC(npc, npc.userID, settings.DespawnTime);
}
GrenadeNPCData.Add(npc.userID, npc);
});
}
}
private void SpawnScarecrow(BasePlayer player, string nadeName, ulong nadeSkin, Vector3 position, NPCPlayerData settings)
{
if (player == null || nadeName == null || nadeSkin == 0 || 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 = nadeSkin;
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<ScarecrowAI>();
brain.Settings = settings;
var move = npc.gameObject.AddComponent<ScarecrowMovement>();
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, nadeName, position))
{
Puts("Spawn Aborted!");
return;
}
GiveGrenadeNpcKit(npc, nadeName);
if (settings.DespawnTime > 0)
{
DespawnNPC(npc, npc.userID, settings.DespawnTime);
}
GrenadeNPCData.Add(npc.userID, npc);
});
}
}
private void SpawnAnimal(BasePlayer player, string nadeName, ulong nadeSkin, Vector3 position)
{
if (player == null || nadeName == null || nadeSkin == 0 || position == null)
return;
if (storedData.AnimalNPC.ContainsKey(nadeName))
{
string npcPrefab = storedData.AnimalNPC[nadeName].Prefab;
string npcName = storedData.AnimalNPC[nadeName].Name;
int spawnAmount = storedData.AnimalNPC[nadeName].NumberToSpawn;
string exploSound = storedData.AnimalNPC[nadeName].ExplosionSound;
for (int i = 0; i < spawnAmount; i++)
{
if (spawnAmount > 1)
{
Vector2 rand;
rand = UnityEngine.Random.insideUnitCircle * storedData.AnimalNPC[nadeName].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 = nadeSkin;
npc.CurrentBehaviour = BaseNpc.Behaviour.Attack;
npc.startHealth = storedData.AnimalNPC[nadeName].Health;
npc.InitializeHealth(storedData.AnimalNPC[nadeName].Health, storedData.AnimalNPC[nadeName].Health);
npc.SetFact(BaseNpc.Facts.CanTargetEnemies, 1, true, true);
npc.SetFact(BaseNpc.Facts.IsAggro, 1, true, true);
if (SpawnAborted(player, npc, nadeName, position))
{
return;
}
float despawnTime = storedData.AnimalNPC[nadeName].DespawnTime;
if (despawnTime > 0)
{
DespawnNPC(npc, npc.net.ID.Value, despawnTime);
}
BaseNpcData.Add(npc.net.ID.Value, npc);
}
}
}
private void SpawnBradley(BasePlayer player, string nadeName, ulong nadeSkin, Vector3 position)
{
if (player == null || nadeName == null || nadeSkin == 0 || position == null)
return;
if (storedData.BradleyNPC.ContainsKey(nadeName))
{
string npcPrefab = storedData.BradleyNPC[nadeName].Prefab;
string npcName = storedData.BradleyNPC[nadeName].Name;
int spawnAmount = storedData.BradleyNPC[nadeName].NumberToSpawn;
string exploSound = storedData.BradleyNPC[nadeName].ExplosionSound;
DoExplosion(exploSound, position);
for (int i = 0; i < spawnAmount; i++)
{
if (spawnAmount > 1)
{
Vector2 rand;
rand = UnityEngine.Random.insideUnitCircle * storedData.AnimalNPC[nadeName].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 = nadeSkin;
npc.Spawn();
if (SpawnAborted(player, npc, nadeName, position))
{
return;
}
float despawnTime = storedData.BradleyNPC[nadeName].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<Rigidbody>();
playerRigidbody.isKinematic = false;
playerRigidbody.useGravity = true;
playerRigidbody.linearDamping = 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 == 0 || 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 == 0)
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<TriggerSafeZone>())
{
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, string nadeName, Vector3 position)
{
if (npc == null || npc.IsDestroyed || nadeName == null)
{
return true;
}
else if (player == null || !player.IsAlive())
{
return true;
}
if (npc.WaterFactor() > 0.7f)
{
NextTick(() => {
npc.Kill();
});
Message(player, "UnderWater", nadeName, npc.net.ID.Value);
return true;
}
else if (IsInSafeZone(position))
{
NextTick(() => {
npc.Kill();
});
Message(player, "InSafeZone", nadeName, npc.net.ID.Value);
return true;
}
else if (!npc.IsOutside())
{
NextTick(() => {
npc.Kill();
});
Message(player, "Inside", nadeName, npc.net.ID.Value);
return true;
}
else if (IsOnStructure(position))
{
NextTick(() => {
npc.Kill();
});
Message(player, "OnStructure", nade
// ... [Code preview truncated due to length. Full source in attached file] ...