C#:
using System;
using Facepunch;
using Newtonsoft.Json.Linq;
using Oxide.Core.Plugins;
using Rust;
using System.Collections;
using System.Collections.Generic;
using System.Reflection;
using UnityEngine;
using UnityEngine.AI;
using Oxide.Plugins.NpcSpawnExtensionMethods;
namespace Oxide.Plugins {
[Info("NpcSpawn", "WebGhost", "2.1.8")]
internal class NpcSpawn : RustPlugin {
#region Config
internal class NpcBelt {
public string ShortName;
public int Amount;
public ulong SkinID;
public List<string> Mods;
}
internal class NpcWear {
public string ShortName;
public ulong SkinID;
}
internal class NpcConfig {
public string Name {
get;
set;
}
public List<NpcWear> WearItems {
get;
set;
}
public List<NpcBelt> BeltItems {
get;
set;
}
public string Kit {
get;
set;
}
public float Health {
get;
set;
}
public float RoamRange {
get;
set;
}
public float ChaseRange {
get;
set;
}
public float DamageScale {
get;
set;
}
public float AimConeScale {
get;
set;
}
public bool DisableRadio {
get;
set;
}
public bool Stationary {
get;
set;
}
public bool CanUseWeaponMounted {
get;
set;
}
public bool CanRunAwayWater {
get;
set;
}
public float Speed {
get;
set;
}
public int AreaMask {
get;
set;
}
public int AgentTypeID {
get;
set;
}
public SensoryStats Sensory {
get;
set;
}
}
public class SensoryStats {
public float AttackRangeMultiplier {
get;
set;
}
public float SenseRange {
get;
set;
}
public float MemoryDuration {
get;
set;
}
public bool CheckVisionCone {
get;
set;
}
public float VisionCone {
get;
set;
}
public void ApplySettingsToBrain(BaseAIBrain<global : : HumanNPC> brain) {
brain.MaxGroupSize = int.MaxValue;
brain.AttackRangeMultiplier = AttackRangeMultiplier;
brain.SenseRange = SenseRange;
brain.MemoryDuration = MemoryDuration;
brain.TargetLostRange = SenseRange * 2f;
brain.CheckVisionCone = CheckVisionCone;
brain.VisionCone = Vector3.Dot(Vector3.forward, Quaternion.Euler(0f, VisionCone, 0f) * Vector3.forward);
}
}
#endregion Config
#region Methods
private ScientistNPC SpawnNpc(Vector3 position, JObject configJson) {
if (Rust.Ai.AiManager.nav_disable) return null;
CustomScientistNpc npc = CreateCustomNpc(position, configJson.ToObject<NpcConfig>());
if (npc != null) _scientists.Add(npc.net.ID, npc);
return npc;
}
private static CustomScientistNpc CreateCustomNpc(Vector3 position, NpcConfig config) {
ScientistNPC scientistNpc = GameManager.server.CreateEntity("assets/rust.ai/agents/npcplayer/humannpc/scientist/scientistnpc_heavy.prefab", position, Quaternion.identity, false) as ScientistNPC;
if (scientistNpc == null) return null;
ScientistBrain scientistBrain = scientistNpc.GetComponent<ScientistBrain>();
CustomScientistNpc customScientist = scientistNpc.gameObject.AddComponent<CustomScientistNpc>();
CustomScientistBrain customScientistBrain = scientistNpc.gameObject.AddComponent<CustomScientistBrain>();
CopySerializableFields<ScientistNPC>(scientistNpc, customScientist);
customScientist.enableSaving = false;
customScientist.Config = config;
customScientistBrain.UseQueuedMovementUpdates = scientistBrain.UseQueuedMovementUpdates;
customScientistBrain.AllowedToSleep = false;
customScientistBrain.DefaultDesignSO = scientistBrain.DefaultDesignSO;
customScientistBrain.Designs = new List<AIDesignSO>(scientistBrain.Designs);
customScientistBrain.InstanceSpecificDesign = scientistBrain.InstanceSpecificDesign;
customScientistBrain.CheckLOS = scientistBrain.CheckLOS;
customScientistBrain.UseAIDesign = true;
customScientistBrain.Pet = false;
UnityEngine.Object.DestroyImmediate(scientistBrain, true);
UnityEngine.Object.DestroyImmediate(scientistNpc, true);
customScientist.gameObject.AwakeFromInstantiate();
customScientist.Spawn();
return customScientist;
}
private static void CopySerializableFields<T>(T src, T dst) {
FieldInfo[] srcFields = typeof(T).GetFields(BindingFlags.Public | BindingFlags.Instance);
foreach (FieldInfo field in srcFields) {
object value = field.GetValue(src);
field.SetValue(dst, value);
}
}
private void AddTargetRaid(CustomScientistNpc npc, BuildingPrivlidge cupboard) {
if (npc == null || cupboard == null || !_scientists.ContainsKey(npc.net.ID)) return;
npc.AddTargetRaid(cupboard);
}
private void AddTargetGuard(CustomScientistNpc npc, BaseEntity target) {
if (npc == null || target == null || !_scientists.ContainsKey(npc.net.ID)) return;
npc.AddTargetGuard(target);
}
#endregion Methods
#region Controller
internal class DefaultSettings {
public float EffectiveRange;
public float AttackLengthMin;
public float AttackLengthMax;
}
private readonly Dictionary<string, DefaultSettings> _weapons = new Dictionary<string, DefaultSettings> {
["rifle.bolt"]
= new DefaultSettings {
EffectiveRange = 150f, AttackLengthMin = -1f, AttackLengthMax = -1f
}
, ["bow.compound"] = new DefaultSettings {
EffectiveRange = 15f, AttackLengthMin = -1f, AttackLengthMax = -1f
}
, ["smg.2"] = new DefaultSettings {
EffectiveRange = 20f, AttackLengthMin = 0.4f, AttackLengthMax = 0.4f
}
, ["shotgun.double"] = new DefaultSettings {
EffectiveRange = 10f, AttackLengthMin = 0.3f, AttackLengthMax = 1f
}
, ["pistol.eoka"] = new DefaultSettings {
EffectiveRange = 10f, AttackLengthMin = -1f, AttackLengthMax = -1f
}
, ["rifle.l96"] = new DefaultSettings {
EffectiveRange = 150f, AttackLengthMin = -1f, AttackLengthMax = -1f
}
, ["pistol.nailgun"] = new DefaultSettings {
EffectiveRange = 15f, AttackLengthMin = 0f, AttackLengthMax = 0.46f
}
, ["pistol.python"] = new DefaultSettings {
EffectiveRange = 15f, AttackLengthMin = 0.175f, AttackLengthMax = 0.525f
}
, ["pistol.semiauto"] = new DefaultSettings {
EffectiveRange = 15f, AttackLengthMin = 0f, AttackLengthMax = 0.46f
}
, ["smg.thompson"] = new DefaultSettings {
EffectiveRange = 20f, AttackLengthMin = 0.4f, AttackLengthMax = 0.4f
}
, ["shotgun.waterpipe"] = new DefaultSettings {
EffectiveRange = 10f, AttackLengthMin = -1f, AttackLengthMax = -1f
}
, ["multiplegrenadelauncher"] = new DefaultSettings {
EffectiveRange = 10f, AttackLengthMin = -1f, AttackLengthMax = -1f
}
, ["snowballgun"] = new DefaultSettings {
EffectiveRange = 5f, AttackLengthMin = 2f, AttackLengthMax = 2f
}
}
;
public class CustomScientistNpc : ScientistNPC, IAIAttack {
public NpcConfig Config {
get;
set;
}
public Transform Transform {
get;
set;
}
public BasePlayer CurrentTarget {
get;
set;
}
public AttackEntity CurrentWeapon {
get;
set;
}
public Vector3 HomePosition {
get;
set;
}
public float DistanceFromBase => Vector3.Distance(Transform.position, HomePosition);
public float DistanceToTarget => Vector3.Distance(Transform.position, CurrentTarget.transform.position);
public override void ServerInit() {
Transform = transform;
HomePosition = Transform.position;
if (NavAgent == null) NavAgent = GetComponent<NavMeshAgent>();
if (NavAgent != null) {
if (Config.AreaMask == 0) {
NavAgent.areaMask = 1;
NavAgent.agentTypeID = -1372625422;
}
else {
NavAgent.areaMask = Config.AreaMask;
NavAgent.agentTypeID = Config.AgentTypeID;
}
}
startHealth = Config.Health;
damageScale = Config.DamageScale;
base.ServerInit();
if (Config.DisableRadio) {
CancelInvoke(PlayRadioChatter);
RadioChatterEffects = Array.Empty<GameObjectRef>();
DeathEffects = Array.Empty<GameObjectRef>();
}
ClearContainer(inventory.containerWear);
ClearContainer(inventory.containerBelt);
if (!string.IsNullOrEmpty(Config.Kit) && _ins.Kits != null) _ins.Kits.Call("GiveKit", this, Config.Kit);
else UpdateInventory();
Invoke(SetDisplayName, 1f);
InvokeRepeating(LightCheck, 1f, 30f);
InvokeRepeating(UpdateTick, 1f, 2f);
}
private void UpdateInventory() {
if (Config.WearItems.Count > 0) {
foreach (Item item in Config.WearItems.Select(x => ItemManager.CreateByName(x.ShortName, 1, x.SkinID))) {
if (item == null) continue;
if (!item.MoveToContainer(inventory.containerWear)) item.Remove();
}
}
if (Config.BeltItems.Count > 0) {
foreach (NpcBelt npcItem in Config.BeltItems) {
Item item = ItemManager.CreateByName(npcItem.ShortName, npcItem.Amount, npcItem.SkinID);
if (item == null) continue;
foreach (ItemDefinition itemDefinition in npcItem.Mods.Select(ItemManager.FindItemDefinition).Where(x => x != null)) item.contents.AddItem(itemDefinition, 1);
if (!item.MoveToContainer(inventory.containerBelt)) item.Remove();
}
}
}
private static void ClearContainer(ItemContainer container) {
List<Item> allItems = container.itemList;
for (int i = allItems.Count - 1; i >= 0; i--) {
Item item = allItems[i];
item.RemoveFromContainer();
item.Remove();
}
}
private void SetDisplayName() => displayName = Config.Name;
private void OnDestroy() {
if (_healCoroutine != null) ServerMgr.Instance.StopCoroutine(_healCoroutine);
if (_fireC4Coroutine != null) ServerMgr.Instance.StopCoroutine(_fireC4Coroutine);
if (_fireRocketLauncherCoroutine != null) ServerMgr.Instance.StopCoroutine(_fireRocketLauncherCoroutine);
CancelInvoke();
}
private void UpdateTick() {
if (CanRunAwayWater()) RunAwayWater();
if (CanThrownGrenade()) ThrownGrenade(CurrentTarget.transform.position);
if (CanHeal()) _healCoroutine = ServerMgr.Instance.StartCoroutine(Heal());
if (inventory.containerBelt.itemList.Any(x => x.info.shortname == "rocket.launcher" || x.info.shortname == "explosive.timed") && Foundations.Count == 0) {
if (CurrentTarget == null) {
FirstTarget = null;
CurrentRaidTarget = null;
}
else {
BuildingBlock block = GetNearEntity<BuildingBlock>(CurrentTarget.transform.position, 0.1f, 1 << 21);
if (block.IsExists() && IsTeam(CurrentTarget, block.OwnerID)) FirstTarget = block;
else {
FirstTarget = null;
CurrentRaidTarget = null;
}
}
}
if (_beforeGuardHomePosition != Vector3.zero) {
if (_guardTarget.IsExists()) HomePosition = _guardTarget.transform.position;
else {
HomePosition = _beforeGuardHomePosition;
_beforeGuardHomePosition = Vector3.zero;
_guardTarget = null;
}
}
}
#region Targeting
public new BasePlayer GetBestTarget() {
BasePlayer target = null;
float delta = -1f;
foreach (BasePlayer basePlayer in Brain.Senses.Memory.Targets.OfType<BasePlayer>()) {
if (!CanTargetBasePlayer(basePlayer)) continue;
float rangeDelta = 1f - Mathf.InverseLerp(1f, Brain.SenseRange, Vector3.Distance(basePlayer.transform.position, Transform.position));
if (Config.Sensory.CheckVisionCone) {
float dot = Vector3.Dot((basePlayer.transform.position - eyes.position).normalized, eyes.BodyForward());
if (dot < Brain.VisionCone) continue;
rangeDelta += Mathf.InverseLerp(Brain.VisionCone, 1f, dot) / 2f;
}
rangeDelta += (Brain.Senses.Memory.IsLOS(basePlayer) ? 2f : 0f);
if (rangeDelta <= delta) continue;
target = basePlayer;
delta = rangeDelta;
}
return target;
}
public bool CanTargetBasePlayer(BasePlayer player) {
if (player == null || player.Health() <= 0f || IsRunAwayWater) return false;
if (player.IsFlying || player.IsSleeping() || player.IsWounded() || player.IsDead() || player.InSafeZone()) return false;
return player.userID.IsSteamId();
}
#endregion Targeting
#region Equip Weapons
private bool _isEquippingWeapon;
public override void EquipWeapon() {
if (!_isEquippingWeapon) {
Item slot = inventory.containerBelt.itemList.FirstOrDefault(x => x.info.shortname != "syringe.medical" && x.info.shortname != "grenade.f1" && x.info.shortname != "grenade.beancan" && x.info.shortname != "grenade.smoke" && x.info.shortname != "explosive.timed" && x.info.shortname != "rocket.launcher");
if (slot != null) StartCoroutine(EquipItem(slot));
}
}
private IEnumerator EquipItem(Item slot = null) {
if (inventory != null && inventory.containerBelt != null) {
_isEquippingWeapon = true;
if (slot == null) {
for (int i = 0; i < inventory.containerBelt.itemList.Count; i++) {
Item item = inventory.containerBelt.GetSlot(i);
if (item?.GetHeldEntity() is AttackEntity) {
slot = item;
break;
}
}
}
if (slot != null) {
if (CurrentWeapon != null) {
CurrentWeapon.SetHeld(false);
CurrentWeapon = null;
SendNetworkUpdate();
inventory.UpdatedVisibleHolsteredItems();
}
yield return CoroutineEx.waitForSeconds(0.5f);
UpdateActiveItem(slot.uid);
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();
if (heldEntity is BaseProjectile && _ins._weapons.ContainsKey(slot.info.shortname)) {
AttackEntity attackEntity = heldEntity as AttackEntity;
attackEntity.effectiveRange = _ins._weapons[slot.info.shortname].EffectiveRange;
attackEntity.attackLengthMin = _ins._weapons[slot.info.shortname].AttackLengthMin;
attackEntity.attackLengthMax = _ins._weapons[slot.info.shortname].AttackLengthMax;
}
}
CurrentWeapon = heldEntity as AttackEntity;
}
_isEquippingWeapon = false;
}
}
internal void HolsterWeapon() {
svActiveItemID = 0;
Item activeItem = GetActiveItem();
if (activeItem != null) {
HeldEntity heldEntity = activeItem.GetHeldEntity() as HeldEntity;
if (heldEntity != null) heldEntity.SetHeld(false);
}
SendNetworkUpdate();
inventory.UpdatedVisibleHolsteredItems();
}
#endregion Equip Weapons
protected override string OverrideCorpseName() => displayName;
public override float GetAimConeScale() => Config.AimConeScale;
#region Heal
private Coroutine _healCoroutine = null;
private bool _isReloadHeal = false;
private bool CanHeal() {
if (_isReloadHeal || health >= Config.Health || CurrentTarget != null || _isEquippingWeapon || IsFireC4 || IsFireRocketLauncher) return false;
return inventory.containerBelt.itemList.Any(x => x.info.shortname == "syringe.medical");
}
private IEnumerator Heal() {
_isReloadHeal = true;
Item syringe = inventory.containerBelt.itemList.FirstOrDefault(x => x.info.shortname == "syringe.medical");
UpdateActiveItem(syringe.uid);
yield return CoroutineEx.waitForSeconds(1.5f);
EquipWeapon();
InitializeHealth(health + 15f > Config.Health ? Config.Health : health + 15f, Config.Health);
if (syringe.amount == 1) syringe.Remove();
else {
syringe.amount--;
syringe.MarkDirty();
}
_isReloadHeal = false;
}
#endregion Heal
#region Grenades
private readonly HashSet<string> _barricades = new HashSet<string> {
"barricade.cover.wood", "barricade.sandbags", "barricade.concrete", "barricade.stone"
}
;
private bool _isReloadGrenade = false;
private bool _isReloadSmoke = false;
private void FinishReloadGrenade() => _isReloadGrenade = false;
private void FinishReloadSmoke() => _isReloadSmoke = false;
private bool CanThrownGrenade() {
if (_isReloadGrenade || CurrentTarget == null) return false;
if (isMounted && !Config.CanUseWeaponMounted) return false;
return DistanceToTarget < 15f && inventory.containerBelt.itemList.Any(x => x.info.shortname == "grenade.f1" || x.info.shortname == "grenade.beancan") && (!CanSeeTarget(CurrentTarget) || IsBehindBarricade());
}
internal bool IsBehindBarricade() => CanSeeTarget(CurrentTarget) && IsBarricade();
private bool IsBarricade() {
SetAimDirection((CurrentTarget.transform.position - Transform.position).normalized);
RaycastHit[] hits = Physics.RaycastAll(eyes.HeadRay());
GamePhysics.Sort(hits);
return hits.Select(x => x.GetEntity() as Barricade).Any(x => x != null && _barricades.Contains(x.ShortPrefabName) && Vector3.Distance(Transform.position, x.transform.position) < DistanceToTarget);
}
private void ThrownGrenade(Vector3 target) {
Item item = inventory.containerBelt.itemList.FirstOrDefault(x => x.info.shortname == "grenade.f1" || x.info.shortname == "grenade.beancan");
if (item != null) {
GrenadeWeapon weapon = item.GetHeldEntity() as GrenadeWeapon;
if (weapon != null) {
Brain.Navigator.Stop();
SetAimDirection((target - Transform.position).normalized);
weapon.ServerThrow(target);
_isReloadGrenade = true;
Invoke(FinishReloadGrenade, 10f);
}
}
}
internal void ThrownSmoke() {
if (!_isReloadSmoke) {
Item item = inventory.containerBelt.itemList.FirstOrDefault(x => x.info.shortname == "grenade.smoke");
if (item != null) {
GrenadeWeapon weapon = item.GetHeldEntity() as GrenadeWeapon;
if (weapon != null) {
weapon.ServerThrow(Transform.position);
_isReloadSmoke = true;
Invoke(FinishReloadSmoke, 30f);
}
}
}
}
#endregion Grenades
#region Run Away Water
internal bool IsRunAwayWater = false;
private bool CanRunAwayWater() {
if (!Config.CanRunAwayWater || IsRunAwayWater) return false;
if (CurrentTarget == null) {
if (Transform.position.y < -0.25f) return true;
else return false;
}
if (Transform.position.y > -0.25f || TerrainMeta.HeightMap.GetHeight(CurrentTarget.transform.position) > -0.25f) return false;
if (CurrentWeapon is BaseProjectile && DistanceToTarget < EngagementRange()) return false;
if (CurrentWeapon is BaseMelee && DistanceToTarget < CurrentWeapon.effectiveRange) return false;
return true;
}
private void RunAwayWater() {
IsRunAwayWater = true;
CurrentTarget = null;
Invoke(FinishRunAwayWater, 20f);
}
private void FinishRunAwayWater() => IsRunAwayWater = false;
#endregion Run Away Water
#region Raid
internal bool IsReloadC4 = false;
internal bool IsReloadRocketLauncher = false;
internal bool IsFireRocketLauncher = false;
internal bool IsFireC4 = false;
private Coroutine _fireC4Coroutine = null;
private Coroutine _fireRocketLauncherCoroutine = null;
internal BaseCombatEntity Turret = null;
internal BaseCombatEntity FirstTarget = null;
internal BuildingPrivlidge MainCupboard = null;
internal HashSet<BoxStorage> Boxes = new HashSet<BoxStorage>();
internal HashSet<BuildingPrivlidge> Cupboards = new HashSet<BuildingPrivlidge>();
internal HashSet<BuildingBlock> Foundations = new HashSet<BuildingBlock>();
internal BaseCombatEntity CurrentRaidTarget = null;
internal void AddTargetRaid(BuildingPrivlidge cupboard) {
Cupboards = GetCupboards(cupboard);
Boxes = GetBoxes(Cupboards);
HashSet<BuildingBlock> allBlocks = GetBlocks(Cupboards);
Foundations = allBlocks.Where(x => x.ShortPrefabName.Contains("foundation"));
Vector3 centerHome = GetCenterHomePos(allBlocks);
MainCupboard = Cupboards.Min(x => Vector3.Distance(x.transform.position, centerHome));
Cupboards.Remove(MainCupboard);
}
internal void AddTurret(BaseCombatEntity turret) {
if (!Turret.IsExists() || Vector3.Distance(Transform.position, turret.transform.position) < Vector3.Distance(Transform.position, Turret.transform.position)) {
Turret = turret;
BuildingBlock block = GetNearEntity<BuildingBlock>(Turret.transform.position, 0.1f, 1 << 21);
CurrentRaidTarget = block.IsExists() ? block : Turret;
}
}
private static T GetNearEntity<T>(Vector3 position, float radius, int layerMask) where T : BaseCombatEntity {
List<T> list = new List<T>();
Vis.Entities(position, radius, list, layerMask);
return list.Count == 0 ? null : list.Min(s => Vector3.Distance(position, s.transform.position));
}
private static List<T> GetEntities<T>(Vector3 position, float radius, int layerMask) where T : BaseCombatEntity {
List<T> list = new List<T>();
Vis.Entities(position, radius, list, layerMask);
return list.Count == 0 ? null : list;
}
private static Vector3 GetCenterHomePos(HashSet<BuildingBlock> blocks) {
float Xmin = blocks.Min(x => x.transform.position.x).transform.position.x;
float Xmax = blocks.Max(x => x.transform.position.x).transform.position.x;
float Ymin = blocks.Min(x => x.transform.position.y).transform.position.y;
float Ymax = blocks.Max(x => x.transform.position.y).transform.position.y;
float Zmin = blocks.Min(x => x.transform.position.z).transform.position.z;
float Zmax = blocks.Max(x => x.transform.position.z).transform.position.z;
return new Vector3((Xmin + Xmax) / 2, (Ymin + Ymax) / 2, (Zmin + Zmax) / 2);
}
private static HashSet<BuildingPrivlidge> GetCupboards(BuildingPrivlidge cupboard) {
HashSet<ulong> ids = cupboard.authorizedPlayers.Select(x => x.userid).ToHashSet();
return GetEntities<BuildingPrivlidge>(cupboard.transform.position, 100f, 1 << 8).Where(x => x.authorizedPlayers.Any(y => ids.Contains(y.userid))).ToHashSet();
}
private static HashSet<BuildingBlock> GetBlocks(HashSet<BuildingPrivlidge> cupboards) => cupboards.SelectMany(x => x.GetBuilding().buildingBlocks);
private static HashSet<BoxStorage> GetBoxes(HashSet<BuildingPrivlidge> cupboards) => cupboards.SelectMany(x => x.GetBuilding().decayEntities.OfType<BoxStorage>());
internal BaseCombatEntity GetRaidTarget() {
BaseCombatEntity result = GetRaidMainTarget();
if (result == null) return null;
BaseCombatEntity targetPath = GetTargetPath(result);
return targetPath != null ? targetPath : result;
}
private BaseCombatEntity GetRaidMainTarget() {
if (Turret.IsExists()) {
BuildingBlock block = GetNearEntity<BuildingBlock>(Turret.transform.position, 0.1f, 1 << 21);
return block.IsExists() ? block : Turret;
}
if (FirstTarget.IsExists()) return FirstTarget;
if (MainCupboard.IsExists()) return MainCupboard;
if (Boxes.Count > 0) {
foreach (BoxStorage storage in Boxes.Where(x => !x.IsExists())) Boxes.Remove(storage);
if (Boxes.Count > 0) return Boxes.Min(x => Vector3.Distance(Transform.position, x.transform.position));
}
if (Cupboards.Count > 0) {
foreach (BuildingPrivlidge cupboard in Cupboards.Where(x => !x.IsExists())) Cupboards.Remove(cupboard);
if (Cupboards.Count > 0) return Cupboards.Min(x => Vector3.Distance(Transform.position, x.transform.position));
}
if (Foundations.Count > 0) {
foreach (BuildingBlock block in Foundations.Where(x => !x.IsExists())) Foundations.Remove(block);
if (Foundations.Count > 0) return Foundations.Min(x => Vector3.Distance(Transform.position, x.transform.position));
}
return null;
}
private BaseCombatEntity GetTargetPath(BaseCombatEntity target) {
NavMeshHit navMeshHit;
int attempts = 0;
while (attempts < 20) {
if (target == null) return null;
attempts++;
float targetHeight = TerrainMeta.HeightMap.GetHeight(target.transform.position);
if (target.transform.position.y - targetHeight > 7.5f) {
List<BuildingBlock> blocks = GetEntities<BuildingBlock>(new Vector3(target.transform.position.x, targetHeight, target.transform.position.z), 15f, 1 << 21);
if (blocks != null) target = blocks.Min(s => Vector3.Distance(s.transform.position, Transform.position));
else return null;
}
if (NavMesh.SamplePosition(target.transform.position, out navMeshHit, 15f, NavAgent.areaMask)) {
NavMeshPath path = new NavMeshPath();
if (NavMesh.CalculatePath(Transform.position, navMeshHit.position, NavAgent.areaMask, path)) return path.status == NavMeshPathStatus.PathComplete ? target : GetNearEntity<BaseCombatEntity>(path.corners.Last(), 5f, 1 << 8 | 1 << 21);
}
float x1 = UnityEngine.Random.Range(target.transform.position.x - 15f - 5f, target.transform.position.x - 15f);
float x2 = UnityEngine.Random.Range(target.transform.position.x + 15f, target.transform.position.x + 15f + 5f);
float z1 = UnityEngine.Random.Range(target.transform.position.z - 15f - 5f, target.transform.position.z - 15f);
float z2 = UnityEngine.Random.Range(target.transform.position.z + 15f, target.transform.position.z + 15f + 5f);
Vector3 vector1 = new Vector3(x1, 500f, z1);
vector1.y = TerrainMeta.HeightMap.GetHeight(vector1);
Vector3 vector2 = new Vector3(x2, 500f, z1);
vector2.y = TerrainMeta.HeightMap.GetHeight(vector2);
Vector3 vector3 = new Vector3(x1, 500f, z2);
vector3.y = TerrainMeta.HeightMap.GetHeight(vector3);
Vector3 vector4 = new Vector3(x2, 500f, z2);
vector4.y = TerrainMeta.HeightMap.GetHeight(vector4);
HashSet<Vector3> list = new HashSet<Vector3> {
vector1, vector2, vector3, vector4
}
;
target = GetNearEntity<BaseCombatEntity>(list.Min(x => Vector3.Distance(Transform.position, x)), 5f, 1 << 8 | 1 << 21);
}
return null;
}
internal bool StartExplosion(BaseCombatEntity target) {
if (isMounted && !Config.CanUseWeaponMounted) return false;
if (target == null) return false;
if (CanThrownC4(target)) {
_fireC4Coroutine = ServerMgr.Instance.StartCoroutine(ThrownC4(target));
return true;
}
if (CanRaidRocketLauncher(target)) {
ThrownSmoke();
_fireRocketLauncherCoroutine = ServerMgr.Instance.StartCoroutine(ProcessFireRocketLauncher(target));
return true;
}
return false;
}
private bool CanRaidRocketLauncher(BaseCombatEntity target) => !IsReloadRocketLauncher && !IsFireRocketLauncher && inventory.containerBelt.itemList.Any(x => x.info.shortname == "rocket.launcher") && Vector3.Distance(Transform.position, target.transform.position) < 15f;
private IEnumerator ProcessFireRocketLauncher(BaseCombatEntity target) {
IsFireRocketLauncher = true;
EquipRocketLauncher();
SetDucked(true);
Brain.Navigator.Stop();
Brain.Navigator.SetFacingDirectionEntity(target);
yield return CoroutineEx.waitForSeconds(1.5f);
if (target.IsExists()) {
if (target.ShortPrefabName.Contains("foundation")) {
Brain.Navigator.ClearFacingDirectionOverride();
SetAimDirection((target.transform.position - new Vector3(0f, 1.5f, 0f) - Transform.position).normalized);
}
FireRocketLauncher();
IsReloadRocketLauncher = true;
Invoke(FinishReloadRocketLauncher, 6f);
}
IsFireRocketLauncher = false;
EquipWeapon();
Brain.Navigator.ClearFacingDirectionOverride();
SetDucked(false);
}
private void EquipRocketLauncher() {
Item item = inventory.containerBelt.itemList.FirstOrDefault(x => x.info.shortname == "rocket.launcher");
UpdateActiveItem(item.uid);
AttackEntity weapon = item.GetHeldEntity() as AttackEntity;
if (weapon != null) CurrentWeapon = weapon;
}
private void FireRocketLauncher() {
RaycastHit raycastHit;
SignalBroadcast(Signal.Attack, string.Empty);
Vector3 vector3 = eyes.position;
Vector3 modifiedAimConeDirection = AimConeUtil.GetModifiedAimConeDirection(2.25f, eyes.BodyForward());
float single = 1f;
if (Physics.Raycast(vector3, modifiedAimConeDirection, out raycastHit, single, 1236478737)) single = raycastHit.distance - 0.1f;
TimedExplosive rocket = GameManager.server.CreateEntity("assets/prefabs/ammo/rocket/rocket_basic.prefab", vector3 + modifiedAimConeDirection * single) as TimedExplosive;
rocket.creatorEntity = this;
ServerProjectile serverProjectile = rocket.GetComponent<ServerProjectile>();
serverProjectile.InitializeVelocity(GetInheritedProjectileVelocity() + modifiedAimConeDirection * serverProjectile.speed);
rocket.Spawn();
}
private void FinishReloadRocketLauncher() => IsReloadRocketLauncher = false;
private bool CanThrownC4(BaseCombatEntity target) => !IsReloadC4 && !IsFireC4 && inventory.containerBelt.itemList.Any(x => x.info.shortname == "explosive.timed") && Vector3.Distance(Transform.position, target.transform.position) < 5f;
private IEnumerator ThrownC4(BaseCombatEntity target) {
Item item = inventory.containerBelt.itemList.FirstOrDefault(x => x.info.shortname == "explosive.timed");
IsFireC4 = true;
Brain.Navigator.Stop();
Brain.Navigator.SetFacingDirectionEntity(target);
yield return CoroutineEx.waitForSeconds(1.5f);
if (target.IsExists()) {
(item.GetHeldEntity() as ThrownWeapon).ServerThrow(target.transform.position);
IsReloadC4 = true;
Invoke(FinishReloadC4, 15f);
}
IsFireC4 = false;
Brain.Navigator.ClearFacingDirectionOverride();
}
private void FinishReloadC4() => IsReloadC4 = false;
private static bool IsTeam(BasePlayer player, ulong targetId) {
if (player == null || targetId == 0) return false;
if (player.userID == targetId) return true;
if (player.currentTeam != 0) {
RelationshipManager.PlayerTeam playerTeam = RelationshipManager.ServerInstance.FindTeam(player.currentTeam);
if (playerTeam == null) return false;
if (playerTeam.members.Contains(targetId)) return true;
}
if (_ins.plugins.Exists("Friends") && (bool)_ins.Friends.Call("AreFriends", player.userID, targetId)) return true;
if (_ins.plugins.Exists("Clans") && (bool)_ins.Clans.Call("IsMemberOrAlly", player.UserIDString, targetId.ToString())) return true;
return false;
}
#endregion Raid
#region Guard
private Vector3 _beforeGuardHomePosition = Vector3.zero;
private BaseEntity _guardTarget = null;
internal void AddTargetGuard(BaseEntity target) {
_beforeGuardHomePosition = HomePosition;
_guardTarget = target;
}
#endregion Guard
#region Multiple Grenade Launcher
internal bool IsReloadGrenadeLauncher = false;
private int _countAmmoInGrenadeLauncher = 6;
internal void FireGrenadeLauncher() {
RaycastHit raycastHit;
SignalBroadcast(Signal.Attack, string.Empty);
Vector3 vector3 = eyes.position;
Vector3 modifiedAimConeDirection = AimConeUtil.GetModifiedAimConeDirection(0.675f, eyes.BodyForward());
float single = 1f;
if (Physics.Raycast(vector3, modifiedAimConeDirection, out raycastHit, single, 1236478737)) single = raycastHit.distance - 0.1f;
TimedExplosive grenade = GameManager.server.CreateEntity("assets/prefabs/ammo/40mmgrenade/40mm_grenade_he.prefab", vector3 + modifiedAimConeDirection * single) as TimedExplosive;
grenade.creatorEntity = this;
ServerProjectile serverProjectile = grenade.GetComponent<ServerProjectile>();
serverProjectile.InitializeVelocity(GetInheritedProjectileVelocity() + modifiedAimConeDirection * serverProjectile.speed);
grenade.Spawn();
_countAmmoInGrenadeLauncher--;
if (_countAmmoInGrenadeLauncher == 0) {
IsReloadGrenadeLauncher = true;
Invoke(FinishReloadGrenadeLauncher, 8f);
}
}
private void FinishReloadGrenadeLauncher() {
_countAmmoInGrenadeLauncher = 6;
IsReloadGrenadeLauncher = false;
}
#endregion Multiple Grenade Launcher
#region Flame Thrower
internal bool IsReloadFlameThrower = false;
internal void FireFlameThrower() {
FlameThrower flameThrower = CurrentWeapon as FlameThrower;
if (flameThrower == null || flameThrower.IsFlameOn()) return;
if (flameThrower.ammo <= 0) {
IsReloadFlameThrower = true;
Invoke(FinishReloadFlameThrower, 4f);
return;
}
flameThrower.SetFlameState(true);
Invoke(flameThrower.StopFlameState, 0.25f);
}
private void FinishReloadFlameThrower() {
FlameThrower flameThrower = CurrentWeapon as FlameThrower;
if (flameThrower == null) return;
flameThrower.TopUpAmmo();
IsReloadFlameThrower = false;
}
#endregion Flame Thrower
}
public class CustomScientistBrain : BaseAIBrain<global : : HumanNPC> {
private CustomScientistNpc _npc = null;
public override void InitializeAI() {
SenseTypes = EntityType.Player;
_npc.Config.Sensory.ApplySettingsToBrain(this);
base.InitializeAI();
UseAIDesign = false;
ThinkMode = AIThinkMode.Interval;
thinkRate = 0.25f;
PathFinder = new HumanPathFinder();
((HumanPathFinder)PathFinder).Init(_npc);
Navigator.DefaultArea = "Walkable";
Navigator.topologyPreference = (TerrainTopology.Enum)1673010749;
Navigator.Speed = _npc.Config.Speed;
}
public override void AddStates() {
_npc = GetEntity() as CustomScientistNpc;
states = new Dictionary<AIState, BasicAIState>();
if (_npc.Config.Stationary) {
AddState(new IdleState(_npc));
AddState(new CombatStationaryState(_npc));
}
else {
AddState(new RoamState(_npc));
AddState(new ChaseState(_npc));
AddState(new CombatState(_npc));
AddState(new MountedState(_npc));
if (_npc.Config.BeltItems.Any(x => x.ShortName == "rocket.launcher" || x.ShortName == "explosive.timed")) AddState(new RaidState(_npc));
}
}
public override void Think(float delta) {
if (_npc == null) return;
Senses.Update();
base.Think(delta);
if (sleeping) return;
if (!_npc.IsRunAwayWater) _npc.CurrentTarget = _npc.GetBestTarget();
}
public override void OnDestroy() {
if (Rust.Application.isQuitting) return;
BaseEntity.Query.Server.RemoveBrain(_npc);
LeaveGroup();
}
public class RoamState : BasicAIState {
private readonly CustomScientistNpc _npc;
public RoamState(CustomScientistNpc npc) : base(AIState.Roam) {
this._npc = npc;
}
public override float GetWeight() => 25f;
public override void StateEnter() {
base.StateEnter();
_npc.SetPlayerFlag(BasePlayer.PlayerFlags.Relaxed, true);
}
public override void StateLeave() {
base.StateLeave();
_npc.ThrownSmoke();
_npc.SetPlayerFlag(BasePlayer.PlayerFlags.Relaxed, false);
}
public override StateStatus StateThink(float delta) {
base.StateThink(delta);
if (_npc.DistanceFromBase > _npc.Config.RoamRange) brain.Navigator.SetDestination(_npc.HomePosition, BaseNavigator.NavigationSpeed.Fast);
else if (!brain.Navigator.Moving) brain.Navigator.SetDestination(GetRoamPosition(), BaseNavigator.NavigationSpeed.Slowest);
return StateStatus.Running;
}
private Vector3 GetRoamPosition() {
Vector2 random = UnityEngine.Random.insideUnitCircle * (_npc.Config.RoamRange - 5f);
Vector3 result = _npc.HomePosition + new Vector3(random.x, 0f, random.y);
NavMeshHit navMeshHit;
if (NavMesh.SamplePosition(result, out navMeshHit, 5f, _npc.NavAgent.areaMask)) {
NavMeshPath path = new NavMeshPath();
if (NavMesh.CalculatePath(_npc.Transform.position, navMeshHit.position, _npc.NavAgent.areaMask, path)) result = path.status == NavMeshPathStatus.PathComplete ? navMeshHit.position : path.corners.Last();
else result = _npc.HomePosition;
}
else result = _npc.HomePosition;
return result;
}
}
public class ChaseState : BasicAIState {
private readonly CustomScientistNpc _npc;
public ChaseState(CustomScientistNpc npc) : base(AIState.Chase) {
this._npc = npc;
}
public override float GetWeight() {
if (_npc.CurrentTarget == null) return 0f;
if (_npc.DistanceFromBase > _npc.Config.ChaseRange) return 0f;
if (_npc.IsRunAwayWater) return 0f;
if (_npc.isMounted) return 0f;
if (_npc.IsFireC4 || _npc.IsFireRocketLauncher) return 0f;
if (_npc.CurrentRaidTarget != null) return 0f;
if (!_npc.CanTargetBasePlayer(_npc.CurrentTarget)) return 0f;
return 50f;
}
public override StateStatus StateThink(float delta) {
base.StateThink(delta);
if (_npc.CurrentTarget == null) return StateStatus.Error;
if (_npc.CurrentWeapon is BaseProjectile) brain.Navigator.SetDestination(brain.PathFinder.GetRandomPositionAround(_npc.CurrentTarget.transform.position, 1f, 2f), _npc.DistanceToTarget >= 10f ? BaseNavigator.NavigationSpeed.Fast : BaseNavigator.NavigationSpeed.Normal);
else brain.Navigator.SetDestination(GetChasePosition(), BaseNavigator.NavigationSpeed.Fast);
return StateStatus.Running;
}
private Vector3 GetChasePosition() {
Vector2 random = UnityEngine.Random.insideUnitCircle * 2f;
Vector3 result = _npc.CurrentTarget.transform.position + new Vector3(random.x, 0f, random.y);
NavMeshHit navMeshHit;
if (NavMesh.SamplePosition(result, out navMeshHit, 2f, _npc.NavAgent.areaMask)) {
NavMeshPath path = new NavMeshPath();
if (NavMesh.CalculatePath(_npc.Transform.position, navMeshHit.position, _npc.NavAgent.areaMa
// ... [Code preview truncated due to length. Full source in attached file] ...