C#:
using Facepunch;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using Oxide.Core.Plugins;
using Rust.Ai.HTN;
using Rust.Ai.HTN.Sensors;
using Rust.Ai.HTN.Reasoning;
using Rust.Ai.HTN.Murderer;
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using UnityEngine;
using UnityEngine.AI;
namespace Oxide.Plugins {
[Info("ZombieHorde", "https://topplugin.ru/", "0.2.4")]
[Description("Плагин куплен на https://topplugin.ru/ | Спасибо за покупку!")]
class ZombieHorde : RustPlugin {
[PluginReference]
Plugin Kits, RandomSpawns, Spawns, Vanish;
private static ZombieHorde Instance {
get;
set;
}
private static bool IsUnloading {
get;
set;
}
private RestrictedTickRate restrictedTickRate;
private List<Vector3> spawnPoints = new List<Vector3>();
private SpawnSystem spawnSystem = SpawnSystem.None;
private ItemDefinition blueprintBase = ItemManager.FindItemDefinition("blueprintbase");
private ItemDefinition glowEyes = ItemManager.FindItemDefinition("gloweyes");
private int navmeshAreaMask;
private const string SCARECROW_PREFAB = "assets/prefabs/npc/scarecrow/scarecrow.prefab";
private const int WORLD_LAYER = 65536;
#region Oxide Hooks
private void OnServerInitialized() {
Instance = this;
IsUnloading = false;
permission.RegisterPermission("zombiehorde.admin", this);
permission.RegisterPermission("zombiehorde.ignore", this);
restrictedTickRate = new GameObject().AddComponent<RestrictedTickRate>();
navmeshAreaMask = GameManager.server.FindPrefab(SCARECROW_PREFAB).GetComponent<HTNPlayer>()?.AiDomain?.NavAgent?.areaMask ?? 1;
VerifyLoadoutProfiles();
VerifySpawnSystem();
FindMonuments();
}
private void OnEntityTakeDamage(BaseCombatEntity entity, HitInfo info) {
HordeMember hordeMember = info?.InitiatorPlayer?.GetComponent<HordeMember>();
if (hordeMember != null) {
if (hordeMember.damageMultiplier != 1f) info.damageTypes.ScaleAll(hordeMember.damageMultiplier);
return;
}
if (configData.Member.HeadshotKills) {
hordeMember = entity.GetComponent<HordeMember>();
if (hordeMember != null && (info?.isHeadshot ?? false)) info.damageTypes.ScaleAll(1000);
}
}
private void OnPlayerDeath(BasePlayer player, HitInfo hitInfo) {
if (player == null || hitInfo == null) return;
HordeMember hordeMember = player.GetComponent<HordeMember>();
if (hordeMember != null) {
if (configData.Loot.DropInventory) hordeMember.PrepareInventory();
hordeMember.Manager.OnMemberDeath(hordeMember);
return;
}
if (configData.Horde.CreateOnDeath) {
HordeMember attacker = hitInfo?.InitiatorPlayer?.GetComponent<HordeMember>();
if (attacker != null) {
for (int i = 0; i < HordeManager._allHordes.Count; i++) HordeManager._allHordes[i].OnPlayerDeath(player, attacker);
}
}
}
private object CanBeTargeted(BaseCombatEntity player, MonoBehaviour behaviour) {
HordeMember hordeMember = player?.GetComponent<HordeMember>();
if (hordeMember != null) {
if (((behaviour is AutoTurret) || (behaviour is GunTrap) || (behaviour is FlameTurret)) && configData.Member.TargetedByTurrets) return null;
return false;
}
return null;
}
private object CanBradleyApcTarget(BradleyAPC bradleyAPC, BaseEntity entity) {
HordeMember hordeMember = entity?.GetComponent<HordeMember>();
if (hordeMember != null && !configData.Member.TargetedByAPC) return false;
return null;
}
private void OnPlayerCorpse(HTNPlayer npc, BaseCorpse corpse) {
HordeMember hordeMember = npc?.GetComponent<HordeMember>();
if (hordeMember == null) return;
LootableCorpse lootableCorpe = corpse as LootableCorpse;
if (lootableCorpe != null) {
lootableCorpe.playerName = npc.displayName;
ClearContainer(lootableCorpe.containers[0]);
ClearContainer(lootableCorpe.containers[2]);
if (configData.Loot.DropInventory) {
ClearContainer(lootableCorpe.containers[1]);
hordeMember.MoveInventoryTo(lootableCorpe);
}
else SpawnIntoContainer(lootableCorpe);
}
}
private void Unload() {
IsUnloading = true;
HordeManager.Order._queue.Clear();
UnityEngine.Object.Destroy(restrictedTickRate.gameObject);
for (int i = HordeManager._allHordes.Count - 1; i >= 0; i--) HordeManager._allHordes[i].Destroy();
HordeManager._allHordes.Clear();
configData = null;
Instance = null;
}
#endregion
#region Functions
#region Horde Spawning
private T ParseType<T>(string type) {
try {
return (T)Enum.Parse(typeof(T), type, true);
}
catch {
return default(T);
}
}
private void VerifyLoadoutProfiles() {
Puts("Verifying horde profiles");
bool hasChanged = false;
for (int i = configData.HordeProfiles.Count - 1; i >= 0; i--) {
string key = configData.HordeProfiles.ElementAt(i).Key;
for (int y = configData.HordeProfiles[key].Count - 1; y >= 0; y--) {
string loadoutId = configData.HordeProfiles[key][y];
if (!configData.Member.Loadouts.Any(x => x.LoadoutID == loadoutId)) {
Puts($"Loadout profile {loadoutId} does not exist.Removing from config");
configData.HordeProfiles[key].Remove(loadoutId);
hasChanged = true;
}
}
if (configData.HordeProfiles[key].Count <= 0) {
Puts($"Horde profile {key} does not have any valid loadouts.Removing from config");
configData.HordeProfiles.Remove(key);
hasChanged = true;
}
}
if (hasChanged) SaveConfig();
}
private bool VerifySpawnSystem() {
spawnSystem = ParseType<SpawnSystem>(configData.Horde.SpawnType);
switch (spawnSystem) {
case SpawnSystem.None : PrintError("You have set an invalid value in the config entry \"Spawn Type\".Unable to spawn hordes!");
return false;
case SpawnSystem.Default : return true;
case SpawnSystem.SpawnsDatabase : if (Spawns != null) {
if (string.IsNullOrEmpty(configData.Horde.SpawnFile)) {
PrintError("You have selected SpawnsDatabase as your method of spawning hordes, however you have not specified a spawn file.Unable to spawn hordes!");
return false;
}
object success = Spawns?.Call("LoadSpawnFile", configData.Horde.SpawnFile);
if (success is List<Vector3>) {
spawnPoints = success as List<Vector3>;
if (spawnPoints.Count == 0) {
PrintError("You have selected SpawnsDatabase as your method of spawning hordes, however the spawn file you have chosen has no spawn points.Unable to spawn hordes!");
return false;
}
return true;
}
}
else PrintError("You have selected SpawnsDatabase as your method of spawning hordes, however SpawnsDatabase is not loaded on your server.Unable to spawn hordes!");
return false;
case SpawnSystem.RandomSpawns : if (RandomSpawns != null) return true;
else PrintError("You have selected RandomSpawns as your method of spawning hordes, however RandomSpawns is not loaded on your server.Unable to spawn hordes!");
return false;
}
return false;
}
private Vector3 GetSpawnPoint() {
switch (spawnSystem) {
case SpawnSystem.None : break;
case SpawnSystem.Default : if (SpawnHandler.Instance != null) {
Vector3 spawnPoint = SpawnHandler.GetSpawnPoint()?.pos ?? Vector3.zero;
if (spawnPoint != Vector3.zero) return spawnPoint;
}
break;
case SpawnSystem.SpawnsDatabase : {
if (Spawns == null) {
PrintError("Tried getting a spawn point but SpawnsDatabase is null.Make sure SpawnsDatabase is still loaded to continue using custom spawn points");
goto case SpawnSystem.Default;
}
Vector3 spawnPoint = spawnPoints.GetRandom();
spawnPoints.Remove(spawnPoint);
if (spawnPoints.Count == 0) spawnPoints = (List<Vector3>)Spawns.Call("LoadSpawnFile", configData.Horde.SpawnFile);
return spawnPoint;
}
case SpawnSystem.RandomSpawns : if (RandomSpawns == null) {
PrintError("Tried getting a spawn point but RandomSpawns is null.Make sure RandomSpawns is still loaded to continue using randomised spawn points");
goto case SpawnSystem.Default;
}
object randomSpawns = RandomSpawns.Call("GetSpawnPoint");
if (randomSpawns != null) return (Vector3)randomSpawns;
break;
}
Vector2 random = UnityEngine.Random.insideUnitCircle * (World.Size / 3f);
Vector3 position = new Vector3(random.x, 0, random.y);
position.y = TerrainMeta.HeightMap.GetHeight(position);
return position;
}
private void FindMonuments() {
int count = 0;
GameObject[] allobjects = UnityEngine.Object.FindObjectsOfType<GameObject>();
foreach (var gobject in allobjects) {
if (count >= configData.Horde.MaximumHordes) break;
if (gobject.name.Contains("autospawn/monument")) {
Transform tr = gobject.transform;
Vector3 position = tr.position;
if (position == Vector3.zero) continue;
if (gobject.name.Contains("powerplant_1") && configData.Monument.Powerplant.Enabled) {
HordeManager.Order.CreateOrder(tr.TransformPoint(new Vector3(-30.8f, 0.2f, -15.8f)), configData.Monument.Powerplant);
count++;
continue;
}
if (gobject.name.Contains("military_tunnel_1") && configData.Monument.Tunnels.Enabled) {
HordeManager.Order.CreateOrder(tr.TransformPoint(new Vector3(-7.4f, 13.4f, 53.8f)), configData.Monument.Tunnels);
count++;
continue;
}
if (gobject.name.Contains("harbor_1") && configData.Monument.LargeHarbor.Enabled) {
HordeManager.Order.CreateOrder(tr.TransformPoint(new Vector3(54.7f, 5.1f, -39.6f)), configData.Monument.LargeHarbor);
count++;
continue;
}
if (gobject.name.Contains("harbor_2") && configData.Monument.SmallHarbor.Enabled) {
HordeManager.Order.CreateOrder(tr.TransformPoint(new Vector3(-66.6f, 4.9f, 16.2f)), configData.Monument.SmallHarbor);
count++;
continue;
}
if (gobject.name.Contains("airfield_1") && configData.Monument.Airfield.Enabled) {
HordeManager.Order.CreateOrder(tr.TransformPoint(new Vector3(-12.4f, 0.2f, -28.9f)), configData.Monument.Airfield);
count++;
continue;
}
if (gobject.name.Contains("trainyard_1") && configData.Monument.Trainyard.Enabled) {
HordeManager.Order.CreateOrder(tr.TransformPoint(new Vector3(35.8f, 0.2f, -0.8f)), configData.Monument.Trainyard);
count++;
continue;
}
if (gobject.name.Contains("water_treatment_plant_1") && configData.Monument.WaterTreatment.Enabled) {
HordeManager.Order.CreateOrder(tr.TransformPoint(new Vector3(11.1f, 0.3f, -80.2f)), configData.Monument.WaterTreatment);
count++;
continue;
}
if (gobject.name.Contains("warehouse") && configData.Monument.Warehouse.Enabled) {
HordeManager.Order.CreateOrder(tr.TransformPoint(new Vector3(-2.8f, 0.1f, -8.1f)), configData.Monument.Warehouse);
count++;
continue;
}
if (gobject.name.Contains("satellite_dish") && configData.Monument.Satellite.Enabled) {
HordeManager.Order.CreateOrder(tr.TransformPoint(new Vector3(18.6f, 6.0f, -7.5f)), configData.Monument.Satellite);
count++;
continue;
}
if (gobject.name.Contains("sphere_tank") && configData.Monument.Dome.Enabled) {
HordeManager.Order.CreateOrder(tr.TransformPoint(new Vector3(-8.8f, 0.7f, 4.5f)), configData.Monument.Dome);
count++;
continue;
}
if (gobject.name.Contains("radtown_small_3") && configData.Monument.Radtown.Enabled) {
HordeManager.Order.CreateOrder(tr.TransformPoint(new Vector3(-16.3f, -2.1f, -3.3f)), configData.Monument.Radtown);
count++;
continue;
}
if (gobject.name.Contains("launch_site_1") && configData.Monument.LaunchSite.Enabled) {
HordeManager.Order.CreateOrder(tr.TransformPoint(new Vector3(222.1f, 3.3f, 0.0f)), configData.Monument.LaunchSite);
count++;
continue;
}
if (gobject.name.Contains("gas_station_1") && configData.Monument.GasStation.Enabled) {
HordeManager.Order.CreateOrder(tr.TransformPoint(new Vector3(-9.8f, 3.0f, 7.2f)), configData.Monument.GasStation);
count++;
continue;
}
if (gobject.name.Contains("supermarket_1") && configData.Monument.Supermarket.Enabled) {
HordeManager.Order.CreateOrder(tr.TransformPoint(new Vector3(3.4f, 0.0f, -8.9f)), configData.Monument.Supermarket);
count++;
continue;
}
if (gobject.name.Contains("mining_quarry_c") && configData.Monument.HQMQuarry.Enabled) {
HordeManager.Order.CreateOrder(tr.TransformPoint(new Vector3(15.8f, 4.5f, -1.5f)), configData.Monument.HQMQuarry);
count++;
continue;
}
if (gobject.name.Contains("mining_quarry_a") && configData.Monument.SulfurQuarry.Enabled) {
HordeManager.Order.CreateOrder(tr.TransformPoint(new Vector3(-0.8f, 0.6f, 11.4f)), configData.Monument.SulfurQuarry);
count++;
continue;
}
if (gobject.name.Contains("mining_quarry_b") && configData.Monument.StoneQuarry.Enabled) {
HordeManager.Order.CreateOrder(tr.TransformPoint(new Vector3(-7.6f, 0.2f, 12.3f)), configData.Monument.StoneQuarry);
count++;
continue;
}
if (gobject.name.Contains("junkyard_1") && configData.Monument.Junkyard.Enabled) {
HordeManager.Order.CreateOrder(tr.TransformPoint(new Vector3(12.8f, 10.8f, -58.4f)), configData.Monument.Junkyard);
count++;
continue;
}
}
}
if (count < configData.Horde.MaximumHordes) CreateRandomHordes();
}
private void CreateRandomHordes() {
int amountToCreate = configData.Horde.MaximumHordes - HordeManager._allHordes.Count;
for (int i = 0; i < amountToCreate; i++) {
float roamDistance = configData.Horde.LocalRoam ? configData.Horde.RoamDistance : -1;
string profile = configData.Horde.UseProfiles ? configData.HordeProfiles.Keys.ToArray().GetRandom() : string.Empty;
HordeManager.Order.CreateOrder(GetSpawnPoint(), configData.Horde.InitialMemberCount, configData.Horde.MaximumMemberCount, roamDistance, profile);
}
}
#endregion
#region Inventory and Loot
private void SpawnIntoContainer(LootableCorpse lootableCorpse) {
int count = UnityEngine.Random.Range(configData.Loot.Random.Minimum, configData.Loot.Random.Maximum);
int spawnedCount = 0;
int loopCount = 0;
while (true) {
loopCount++;
if (loopCount > 3) return;
float probability = UnityEngine.Random.Range(0f, 1f);
List<ConfigData.LootTable.RandomLoot.LootDefinition> definitions = new List<ConfigData.LootTable.RandomLoot.LootDefinition>(configData.Loot.Random.List);
for (int i = 0; i < configData.Loot.Random.List.Count; i++) {
ConfigData.LootTable.RandomLoot.LootDefinition lootDefinition = definitions.GetRandom();
definitions.Remove(lootDefinition);
if (lootDefinition.Probability >= probability) {
CreateItem(lootDefinition, lootableCorpse.containers[0]);
spawnedCount++;
if (spawnedCount >= count) return;
}
}
}
}
private void CreateItem(ConfigData.LootTable.RandomLoot.LootDefinition lootDefinition, ItemContainer container) {
Item item = null;
if (!lootDefinition.IsBlueprint) item = ItemManager.CreateByName(lootDefinition.Shortname, lootDefinition.GetAmount(), lootDefinition.SkinID);
else {
item = ItemManager.Create(blueprintBase);
item.blueprintTarget = ItemManager.FindItemDefinition(lootDefinition.Shortname).itemid;
}
if (item != null) {
item.OnVirginSpawn();
if (!item.MoveToContainer(container, -1, true)) item.Remove(0f);
}
if (lootDefinition.Required != null) CreateItem(lootDefinition.Required, container);
}
private static void StripInventory(BasePlayer player, bool skipWear = false) {
List<Item> list = Pool.GetList<Item>();
player.inventory.AllItemsNoAlloc(ref list);
for (int i = list.Count - 1; i >= 0; i--) {
Item item = list[i];
if (skipWear && item?.parent == player.inventory.containerWear) continue;
item.RemoveFromContainer();
item.Remove();
}
Pool.FreeList(ref list);
}
private static void ClearContainer(ItemContainer container, bool skipWear = false) {
if (container == null || container.itemList == null) return;
while (container.itemList.Count > 0) {
Item item = container.itemList[0];
item.RemoveFromContainer();
item.Remove(0f);
}
}
#endregion
#region Spawning
private static HTNPlayer InstantiateEntity(Vector3 position) {
GameObject gameObject = Facepunch.Instantiate.GameObject(GameManager.server.FindPrefab(SCARECROW_PREFAB), position, new Quaternion());
gameObject.name = SCARECROW_PREFAB;
UnityEngine.SceneManagement.SceneManager.MoveGameObjectToScene(gameObject, Rust.Server.EntityScene);
UnityEngine.Object.Destroy(gameObject.GetComponent<Spawnable>());
if (!gameObject.activeSelf) gameObject.SetActive(true);
HTNPlayer component = gameObject.GetComponent<HTNPlayer>();
return component;
}
private static NavMeshHit navHit;
private static RaycastHit raycastHit;
private static object FindPointOnNavmesh(Vector3 targetPosition, float maxDistance = 4f) {
List<Collider> list = Pool.GetList<Collider>();
try {
for (int i = 0; i < 10; i++) {
list.Clear();
if (NavMesh.SamplePosition(targetPosition, out navHit, maxDistance, Instance.navmeshAreaMask)) {
if (IsNearOrInWorldCollider(list, navHit.position)) continue;
float terrainHeight = TerrainMeta.HeightMap.GetHeight(navHit.position);
if (terrainHeight > navHit.position.y || navHit.position.y - terrainHeight > 3f) continue;
return navHit.position;
}
}
return null;
}
finally {
Pool.FreeList(ref list);
}
}
private static bool IsNearOrInWorldCollider(List<Collider> list, Vector3 position) {
GamePhysics.OverlapSphere(position, 1f, list, WORLD_LAYER, QueryTriggerInteraction.Ignore);
list.RemoveAll(x => x.gameObject.name.Contains("road"));
return list.Count > 0;
}
#endregion
#endregion
#region Components
private class RestrictedTickRate : MonoBehaviour {
private int lastIndex = 0;
private float nextTick;
private const float TICK_RATE = 0.2f;
private void Update() {
int count = HordeManager._allHordes?.Count ?? 0;
if (Time.time < nextTick || count == 0) return;
if (lastIndex >= count - 1) lastIndex = 0;
HordeManager manager = HordeManager._allHordes[lastIndex];
if (manager.members.Count == 0) manager.Destroy();
else manager.HordeTick();
lastIndex++;
nextTick = Time.time + TICK_RATE;
}
}
internal class HordeManager {
internal static List<HordeManager> _allHordes = new List<HordeManager>();
internal List<HordeMember> members;
internal Vector3 Destination;
internal Vector3 AverageLocation;
internal BaseEntity PrimaryTarget;
internal NpcPlayerInfo NpcPlayerInfo;
internal AnimalInfo AnimalInfo;
private bool knownTargetNull = false;
private bool isRoaming = true;
private bool isRegrouping = false;
private Vector3 initialSpawnPosition;
private int initialMemberCount;
private bool isLocalHorde = false;
private float maximumRoamDistance;
internal string hordeProfile;
private float nextGrowthTime = Time.time + configData.Horde.GrowthRate;
private int maximumMemberCount;
private float nextMergeTime = Time.time + MERGE_COOLDOWN;
private float refreshRoamTime;
private const float MERGE_COOLDOWN = 180f;
private const float ROAM_REFRESH_RATE = 3f;
internal static bool Create(Order order) {
HordeManager manager = new HordeManager();
manager.members = Pool.GetList<HordeMember>();
manager.initialSpawnPosition = order.position;
if (order.maximumRoamDistance > 0) {
manager.isLocalHorde = true;
manager.maximumRoamDistance = order.maximumRoamDistance;
}
manager.initialMemberCount = order.initialMemberCount;
manager.maximumMemberCount = order.maximumMemberCount;
manager.hordeProfile = order.hordeProfile;
for (int i = 0; i < order.initialMemberCount; i++) manager.SpawnMember(order.position, false);
if (manager.members.Count == 0) {
manager.Destroy(true);
return false;
}
manager.SetPrimaryTarget(null, default(NpcPlayerInfo));
_allHordes.Add(manager);
return true;
}
internal void Destroy(bool permanent = false) {
for (int i = members.Count - 1; i >= 0; i--) {
HTNPlayer npc = members[i]?.NPC;
if (npc != null && !npc.IsDestroyed) npc.Kill();
}
Pool.FreeList(ref members);
_allHordes.Remove(this);
if (!IsUnloading) {
InvokeHandler.Invoke(Instance.restrictedTickRate, () => Order.CreateOrder(isLocalHorde ? initialSpawnPosition : Instance.GetSpawnPoint(), initialMemberCount, maximumMemberCount, isLocalHorde ? maximumRoamDistance : -1f, hordeProfile), configData.Horde.RespawnTime);
}
}
internal void HordeTick() {
if (members.Count == 0) return;
AverageLocation = GetAverageVector();
TryMergeHordes();
TryGrowHorde();
if (!knownTargetNull && !IsValidTarget(PrimaryTarget)) {
SetPrimaryTarget(null, default(NpcPlayerInfo));
knownTargetNull = true;
}
bool hasSeentargetRecently = false;
float currentTime = Time.time;
for (int i = 0; i < members.Count; i++) {
HordeMember member = members[i];
member.OnTick();
if (!isRoaming && !hasSeentargetRecently) {
if (currentTime - member.lastSeenTargetTime < configData.Horde.ForgetTime) {
hasSeentargetRecently = true;
if (PrimaryTarget != null) Destination = PrimaryTarget.ServerPosition;
}
}
}
if (!isRoaming && !hasSeentargetRecently) {
isRoaming = true;
SendMemberUpdate(default(NpcPlayerInfo));
return;
}
if (isRoaming) {
if (Time.time > refreshRoamTime) {
refreshRoamTime = Time.time + ROAM_REFRESH_RATE;
if (GetMaximumSeperation() > 10f && !isRegrouping) {
isRegrouping = true;
Destination = AverageLocation;
SendMemberUpdate(default(NpcPlayerInfo));
return;
}
if (Vector3.Distance(Destination, AverageLocation) < 5f) {
isRegrouping = false;
Destination = GetRandomLocation();
SendMemberUpdate(default(NpcPlayerInfo));
return;
}
UpdateRoamTarget();
}
}
}
internal void EvaluateTarget(NpcPlayerInfo target) {
if (PrimaryTarget == target.Player) return;
if (PrimaryTarget == null) {
SetPrimaryTarget(target.Player, target);
return;
}
if (Vector3.Distance(AverageLocation, target.Player.transform.position) < Vector3.Distance(AverageLocation, PrimaryTarget.transform.position) || PrimaryTarget is BaseNpc) {
SetPrimaryTarget(target.Player, target);
return;
}
}
internal void EvaluateTarget(AnimalInfo target) {
if (PrimaryTarget == target.Animal) return;
if (PrimaryTarget == null) {
SetPrimaryTarget(target.Animal, target);
return;
}
if (PrimaryTarget is BasePlayer) return;
if (Vector3.Distance(AverageLocation, target.Animal.transform.position) < Vector3.Distance(AverageLocation, PrimaryTarget.transform.position)) {
SetPrimaryTarget(target.Animal, target);
return;
}
}
internal void SetPrimaryTarget(BaseEntity target, NpcPlayerInfo info) {
SetPrimaryTarget(target);
PrimaryTarget = target;
NpcPlayerInfo = info;
AnimalInfo = default(AnimalInfo);
for (int i = 0; i < members.Count; i++) {
members[i].OnTargetUpdated(info);
}
}
internal void SetPrimaryTarget(BaseEntity target, AnimalInfo info) {
SetPrimaryTarget(target);
PrimaryTarget = target;
NpcPlayerInfo = default(NpcPlayerInfo);
AnimalInfo = info;
SendMemberUpdate(info);
}
internal void SetPrimaryTarget(BaseEntity target) {
if (target == null) {
NpcPlayerInfo = default(NpcPlayerInfo);
AnimalInfo = default(AnimalInfo);
isRoaming = true;
Destination = GetRandomLocation();
}
else {
knownTargetNull = false;
isRoaming = false;
isRegrouping = false;
Destination = target.transform.position;
}
}
private void SendMemberUpdate(NpcPlayerInfo info) {
for (int i = 0; i < members.Count; i++) {
members[i]?.OnTargetUpdated(info);
}
}
private void SendMemberUpdate(AnimalInfo info) {
for (int i = 0; i < members.Count; i++) {
members[i]?.OnTargetUpdated(info);
}
}
private void UpdateRoamTarget() {
for (int i = 0; i < members.Count; i++) {
members[i]?.SetRoamToDestination();
}
}
internal Vector3 GetAverageVector() {
Vector3 location = Vector3.zero;
if (members.Count == 0) return location;
int count = 0;
for (int i = 0; i < members.Count; i++) {
HordeMember hordeMember = members[i];
if (hordeMember == null || hordeMember.NPC == null) continue;
location += hordeMember.transform.position;
count++;
}
return location /= count;
}
private Vector3 GetRandomLocation() {
Vector3 position = Vector3.zero;
if (isLocalHorde) {
Vector2 randomInCircle = UnityEngine.Random.insideUnitCircle * maximumRoamDistance;
position = initialSpawnPosition + new Vector3(randomInCircle.x, 0, randomInCircle.y);
position.y = TerrainMeta.HeightMap.GetHeight(position);
}
else {
if (Instance.RandomSpawns) {
object success = Instance.RandomSpawns.Call("GetSpawnPoint");
if (success is Vector3) position = (Vector3)success;
}
if (position == Vector3.zero) {
Vector2 randomInCircle = UnityEngine.Random.insideUnitCircle * (World.Size / 3f);
position.x = randomInCircle.x;
position.z = randomInCircle.y;
position.y = TerrainMeta.HeightMap.GetHeight(position);
}
}
if (NavMesh.SamplePosition(position, out navHit, 150, Instance.navmeshAreaMask)) return navHit.position;
return position;
}
private float GetMaximumSeperation() {
float distance = 0;
for (int i = 0; i < members.Count; i++) {
HordeMember member = members[i];
if (member?.NPC != null) {
float d = Vector3.Distance(members[i].NPC.transform.position, AverageLocation);
if (d > distance) distance = d;
}
}
return distance;
}
internal bool IsValidTarget(BaseEntity target) {
if (target == null || target.IsDestroyed) return false;
if (target.Health() <= 0f) return false;
if (target is BasePlayer) {
BasePlayer player = target as BasePlayer;
if (Instance.Vanish != null && (bool)Instance.Vanish?.Call("IsInvisible", player)) return false;
if (player.IsFlying) return false;
if (player.IsNpc) {
if ((player as HTNPlayer)?.AiDefinition?.Info?.Family == BaseNpcDefinition.Family.Murderer) return false;
if (!configData.Member.TargetNPCs && (player is Scientist || player is ScientistNPC || player is HTNPlayer || player is NPCPlayerApex)) return false;
}
if (player.HasPlayerFlag(BasePlayer.PlayerFlags.SafeZone)) return false;
if (configData.Member.IgnoreSleepers && player.IsSleeping()) return false;
if (Instance.permission.UserHasPermission(player.UserIDString, "zombiehorde.ignore")) return false;
}
return true;
}
internal bool SpawnMember(Vector3 position, bool alreadyInitialized = true) {
Vector2 random = UnityEngine.Random.insideUnitCircle * 3;
Vector3 spawnPosition = Vector3.zero;
object success = FindPointOnNavmesh(position + new Vector3(random.x, 0, random.y), maximumRoamDistance == -1 ? 4f : 1f);
if (success == null) {
if (members?.Count > 0) {
spawnPosition = members.GetRandom().transform.position;
Vector2 r = UnityEngine.Random.insideUnitCircle.normalized;
spawnPosition += new Vector3(r.x, 0f, r.y);
}
else return false;
}
else spawnPosition = (Vector3)success;
HTNPlayer npc = InstantiateEntity(spawnPosition);
if (npc == null) return false;
npc.enableSaving = false;
npc.Spawn();
HordeMember member = npc.gameObject.AddComponent<HordeMember>();
member.Manager = this;
members.Add(member);
if (alreadyInitialized) {
if (PrimaryTarget == null || PrimaryTarget is BasePlayer) member.OnTargetUpdated(NpcPlayerInfo);
else if (PrimaryTarget is BaseNpc) member.OnTargetUpdated(AnimalInfo);
}
return true;
}
internal void OnPlayerDeath(BasePlayer player, HordeMember hordeMember) {
if (hordeMember == null || !members.Contains(hordeMember)) return;
if (members.Count < maximumMemberCount) SpawnMember(player.transform.position);
}
internal void OnMemberDeath(HordeMember hordeMember) {
members.Remove(hordeMember);
if (members.Count == 0) Destroy();
}
private void TryGrowHorde() {
if (nextGrowthTime < Time.time) {
if (members.Count < maximumMemberCount) {
for (int i = 0; i < 5; i++) {
if (SpawnMember(AverageLocation)) break;
}
}
nextGrowthTime = Time.time + configData.Horde.GrowthRate;
}
}
private void TryMergeHordes() {
if (!configData.Horde.MergeHordes || nextMergeTime > Time.time) return;
for (int y = _allHordes.Count - 1; y >= 0; y--) {
HordeManager manager = _allHordes[y];
if (manager == this) continue;
if (members.Count >= maximumMemberCount) return;
if (Vector3.Distance(AverageLocation, manager.AverageLocation) < 20) {
int amountToMerge = maximumMemberCount - members.Count;
if (amountToMerge >= manager.members.Count) {
for (int i = 0; i < manager.members.Count; i++) {
HordeMember member = manager.members[i];
members.Add(member);
member.Manager = this;
}
manager.members.Clear();
manager.Destroy();
nextMergeTime = Time.time + MERGE_COOLDOWN;
}
else {
bool hasMerged = false;
for (int i = 0; i < amountToMerge; i++) {
if (manager.members.Count > 0) {
HordeMember member = manager.members[0];
members.Add(member);
member.Manager = this;
manager.members.Remove(member);
hasMerged = true;
}
}
if (hasMerged) nextMergeTime = Time.time + MERGE_COOLDOWN;
}
}
}
}
public class Order {
public Vector3 position;
public int initialMemberCount;
public int maximumMemberCount;
public float maximumRoamDistance;
public string hordeProfile;
public Order(Vector3 position, int initialMemberCount, string hordeProfile) {
this.position = position;
this.initialMemberCount = initialMemberCount;
maximumMemberCount = configData.Horde.MaximumMemberCount;
maximumRoamDistance = -1f;
this.hordeProfile = hordeProfile;
}
public Order(Vector3 position, int initialMemberCount, int maximumMemberCount, float maximumRoamDistance, string hordeProfile) {
this.position = position;
this.initialMemberCount = initialMemberCount;
this.maximumMemberCount = maximumMemberCount;
this.maximumRoamDistance = maximumRoamDistance;
this.hordeProfile = hordeProfile;
}
internal static Queue<Order> _queue = new Queue<Order>();
private static bool IsRunning {
get;
set;
}
public static void CreateOrder(Vector3 position, int initialMemberCount, int maximumMemberCount, float maximumRoamDistance, string hordeProfile) {
_queue.Enqueue(new Order(position, initialMemberCount, maximumMemberCount, maximumRoamDistance, hordeProfile));
if (!IsRunning) ServerMgr.Instance.StartCoroutine(ProcessOrders());
}
public static void CreateOrder(Vector3 position, ConfigData.MonumentSpawn.MonumentSettings settings) {
object success = FindPointOnNavmesh(position, 0.1f);
if (position == null) return;
_queue.Enqueue(new Order(position, configData.Horde.InitialMemberCount, settings.HordeSize, settings.RoamDistance, settings.Profile));
if (!IsRunning) ServerMgr.Instance.StartCoroutine(ProcessOrders());
}
private static IEnumerator ProcessOrders() {
IsRunning = true;
while (HordeManager._allHordes?.Count >= configData.Horde.MaximumHordes) {
yield return CoroutineEx.waitForSeconds(10f);
}
Order order = _queue.Dequeue();
if (order != null) HordeManager.Create(order);
if (_queue.Count > 0) {
yield return CoroutineEx.waitForSeconds(3f);
yield return ServerMgr.Instance.StartCoroutine(ProcessOrders());
}
IsRunning = false;
}
}
}
internal class HordeMember : MonoBehaviour {
internal HTNPlayer NPC {
get;
private set;
}
internal MurdererDomain Domain {
get;
private set;
}
internal MurdererContext Context {
get;
private set;
}
internal MurdererMemory Memory {
get;
private set;
}
internal HordeManager Manager {
get;
set;
}
internal List<AnimalInfo> animalsLineOfSight = Pool.GetList<AnimalInfo>();
internal float damageMultiplier;
internal float lastSeenTargetTime;
private ItemContainer[] containers;
private void Awake() {
NPC = GetComponent<HTNPlayer>();
Domain = NPC.AiDomain as MurdererDomain;
Context = Domain.MurdererContext;
Memory = Context.Memory;
InitializeSensorsAndReasoners();
InitializeNpc();
}
private void OnDestroy() {
Pool.Free(ref animalsLineOfSight);
}
private void InitializeSensorsAndReasoners() {
int index = Domain.Reasoners.FindIndex(x => x is Rust.Ai.HTN.Murderer.Reasoners.ReturnHomeReasoner);
Domain.Reasoners.RemoveAt(index);
index = Domain.Sensors.FindIndex(x => x is Rust.Ai.HTN.Sensors.PlayersInRangeSensor);
Domain.Sensors.RemoveAt(index);
Domain.Sensors.Insert(index, new PlayersInRangeSensor(this));
index = Domain.Sensors.FindIndex(x => x is Rust.Ai.HTN.Murderer.Sensors.AnimalsInRangeSensor);
Domain.Sensors.RemoveAt(index);
if (configData.Member.TargetAnimals) {
Domain.Sensors.Insert(index, new AnimalsInRangeSensor(this));
index = Domain.Reasoners.FindIndex(x => x is Rust.Ai.HTN.Murderer.Reasoners.EnemyRangeReasoner);
Domain.Reasoners.RemoveAt(index);
Domain.Reasoners.Add(new EnemyRangeReasoner(this));
index = Domain.Reasoners.FindIndex(x => x is Rust.Ai.HTN.Murderer.Reasoners.EnemyTargetReasoner);
Domain.Reasoners.RemoveAt(index);
Domain.Reasoners.Add(new EnemyTargetReasoner(this));
index = Domain.Reasoners.FindIndex(x => x is Rust.Ai.HTN.Murderer.Reasoners.EnemyPlayerLineOfSightReasoner);
Domain.Reasoners.RemoveAt(index);
Domain.Reasoners.Add(new EnemyPlayerLineOfSightReasoner(this));
index = Domain.Reasoners.FindIndex(x => x is Rust.Ai.HTN.Murderer.Reasoners.FireTacticReasoner);
Domain.Reasoners.RemoveAt(index);
Domain.Reasoners.Add(new FireTacticReasoner(this));
index = Domain.Reasoners.FindIndex(x => x is Rust.Ai.HTN.Murderer.Reasoners.PreferredFightingRangeReasoner);
Domain.Reasoners.RemoveAt(index);
Domain.Reasoners.Add(new PreferredFightingRangeReasoner(this));
index = Domain.Reasoners.FindIndex(x => x is Rust.Ai.HTN.Murderer.Reasoners.AtLastKnownEnemyPlayerLocationReasoner);
Domain.Reasoners.RemoveAt(index);
Domain.Reasoners.Add(new AtLastKnownEnemyPlayerLocationReasoner(this));
}
}
private void InitializeNpc() {
StripInventory(NPC);
InvokeHandler.Invoke(NPC, SpawnLoadout, 0.1f);
}
private void SpawnLoadout() {
ConfigData.MemberOptions.Loadout loadout;
if (!string.IsNullOrEmpty(Manager.hordeProfile) && configData.HordeProfiles.ContainsKey(Manager.hordeProfile)) {
string loadoutId = configData.HordeProfiles[Manager.hordeProfile].GetRandom();
loadout = configData.Member.Loadouts.Find(x => x.LoadoutID == loadoutId);
}
else loadout = configData.Member.Loadouts.GetRandom();
var Max = 8730;
if (Max == 0) {
}
NPC.displayName = loadout.Names.Length > 0 ? loadout.Names.GetRandom() : "Zombie";
NPC.InitializeHealth(loadout.Health, loadout.Health);
NPC.AiDefinition.Sensory.VisionRange = loadout.VisionRange;
NPC.AiDefinition.Sensory.HearingRange = loadout.VisionRange * 0.5f;
NPC.AiDefinition.Movement.RunSpeed = loadout.MovementSpeed;
damageMultiplier = loadout.DamageMultiplier;
for (int i = 0; i < loadout.BeltItems.Count; i++) {
ConfigData.LootTable.InventoryItem loadoutItem = loadout.BeltItems[i];
ItemManager.CreateByName(loadoutItem.Shortname, loadoutItem.Amount, loadoutItem.SkinID).MoveToContainer(NPC.inventory.containerBelt);
}
for (int i = 0; i < loadout.MainItem
// ... [Code preview truncated due to length. Full source in attached file] ...