C#:
using Newtonsoft.Json;
using UnityEngine;
using UnityEngine.AI;
using System;
using System.Collections.Generic;
using Oxide.Core;
using Oxide.Core.Plugins;
using Oxide.Plugins.NpcMovingControlExtensionMethods;
namespace Oxide.Plugins
{
[Info("NpcMovingControl", "Adem", "1.0.6")]
class NpcMovingControl : RustPlugin
{
#region Variables
static NpcMovingControl ins;
[PluginReference] private Plugin BotReSpawn;
HashSet<string> subscribeMethods = new HashSet<string>
{
"OnEntitySpawned",
"OnCorpsePopulate"
};
#endregion Variables
#region Hooks
void Init()
{
Unsubscribes();
}
void OnServerInitialized()
{
ins = this;
UpdateConfig();
Subscribes();
UpdateAllNpc();
LootManager.InitialLootManagerUpdate();
}
void Unload()
{
ins = null;
}
void OnEntitySpawned(ScarecrowNPC scarecrowNPC)
{
if (scarecrowNPC == null)
return;
UpdateScarecrow(scarecrowNPC);
}
void OnEntitySpawned(ScientistNPC scientistNPC)
{
if (scientistNPC != null)
UpdateNpc(scientistNPC);
}
void OnCorpsePopulate(ScarecrowNPC scarecrowNPC, NPCPlayerCorpse corpse)
{
if (scarecrowNPC == null || corpse == null)
return;
if (ins._config.scareCrowLootPrefabName == "")
return;
ins.NextTick(() =>
{
if (corpse == null)
return;
LootManager.UpdateScareCrowLoot(corpse.containers[0]);
});
}
#endregion Hooks
#region Methods
void UpdateConfig()
{
if (_config.version != Version)
{
if (_config.version.Patch == 0)
_config.scareCrowLootPrefabName = "assets/rust.ai/agents/npcplayer/humannpc/scientist/scientistnpc_roam.prefab";
if (_config.version.Patch <= 3)
{
_config.scareCrowHealthScale = 0.5f;
_config.scarecrowWearConfigs = new List<WearConfig>
{
new WearConfig
{
wearItems = new HashSet<WearItemConfig>
{
new WearItemConfig
{
shortName = "frankensteins.monster.01.head",
skinID = 0,
},
new WearItemConfig
{
shortName = "frankensteins.monster.01.torso",
skinID = 0,
},
new WearItemConfig
{
shortName = "frankensteins.monster.01.legs",
skinID = 0,
}
}
},
new WearConfig
{
wearItems = new HashSet<WearItemConfig>
{
new WearItemConfig
{
shortName = "frankensteins.monster.02.head",
skinID = 0,
},
new WearItemConfig
{
shortName = "frankensteins.monster.02.torso",
skinID = 0,
},
new WearItemConfig
{
shortName = "frankensteins.monster.02.legs",
skinID = 0,
}
}
},
new WearConfig
{
wearItems = new HashSet<WearItemConfig>
{
new WearItemConfig
{
shortName = "frankensteins.monster.03.head",
skinID = 0,
},
new WearItemConfig
{
shortName = "frankensteins.monster.03.torso",
skinID = 0,
},
new WearItemConfig
{
shortName = "frankensteins.monster.03.legs",
skinID = 0,
}
}
}
};
}
if (_config.scareCrowLootPrefabName == null)
_config.scareCrowLootPrefabName = "";
_config.version = Version;
SaveConfig();
}
}
void Unsubscribes()
{
foreach (string hook in subscribeMethods)
Unsubscribe(hook);
}
void Subscribes()
{
foreach (string hook in subscribeMethods)
Subscribe(hook);
}
void UpdateAllNpc()
{
foreach (ScientistNPC scientistNPC in BaseNetworkable.serverEntities.OfType<ScientistNPC>())
if (scientistNPC != null)
UpdateNpc(scientistNPC);
}
void UpdateScarecrow(ScarecrowNPC scarecrowNPC)
{
float newHealth = scarecrowNPC.MaxHealth() * _config.scareCrowHealthScale;
scarecrowNPC.startHealth = newHealth;
scarecrowNPC.InitializeHealth(newHealth, newHealth);
if (_config.scarecrowPatrolRadius > -1)
{
scarecrowNPC.RoamAroundHomePoint = true;
if (_config.scarecrowPatrolRadius > 0)
{
NextTick(() =>
{
if (scarecrowNPC != null && scarecrowNPC.Brain != null)
scarecrowNPC.Brain.Navigator.BestCoverPointMaxDistance = _config.scarecrowPatrolRadius;
});
}
}
WearConfig wearConfig = _config.scarecrowWearConfigs.GetRandom();
if (wearConfig != null)
ApplyWearConfigToNpc(scarecrowNPC, wearConfig);
}
void UpdateNpc(ScientistNPC scientistNPC)
{
TryWearNpc(scientistNPC);
if (scientistNPC.ShortPrefabName == "scientistnpc_roam" && _config.npcPatrolRadius > 0)
timer.In(2f, () => TryModifyNpc(scientistNPC));
}
void TryWearNpc(ScientistNPC scientistNPC)
{
CustomNpcWearConfig customNpcWearConfig = _config.customNpcWearConfigs.FirstOrDefault(x => x.npcShortPrefabName == scientistNPC.ShortPrefabName);
if (customNpcWearConfig != null)
{
WearConfig wearConfig = customNpcWearConfig.wearConfigs.GetRandom();
if (wearConfig != null)
ApplyWearConfigToNpc(scientistNPC, wearConfig);
}
}
void ApplyWearConfigToNpc(NPCPlayer npcPlayer, WearConfig wearConfig)
{
npcPlayer.inventory.containerWear.ClearItemsContainer();
foreach (WearItemConfig wearItemConfig in wearConfig.wearItems)
{
Item item = ItemManager.CreateByName(wearItemConfig.shortName, skin: wearItemConfig.skinID);
if (item != null)
item.MoveToContainer(npcPlayer.inventory.containerWear);
}
}
void TryModifyNpc(ScientistNPC scientistNPC)
{
if (scientistNPC == null || scientistNPC.Brain == null || scientistNPC.isMounted)
return;
if (scientistNPC.GetType().ToString() != "ScientistNPC" || scientistNPC.Brain.GetType().ToString() != "ScientistBrain")
return;
if (plugins.Exists("BotReSpawn") && (bool)BotReSpawn?.Call("IsBotReSpawn", (ulong)scientistNPC.userID))
return;
if (scientistNPC.Brain.states.ContainsKey(AIState.FollowPath))
{
scientistNPC.Brain.states[AIState.Roam] = new RoamState(scientistNPC.transform.position, _config.npcPatrolRadius, scientistNPC);
scientistNPC.Brain.CurrentState.StateLeave(scientistNPC.Brain, scientistNPC);
scientistNPC.Brain.states.Remove(AIState.FollowPath);
scientistNPC.Brain.CurrentState = scientistNPC.Brain.states[AIState.Roam];
scientistNPC.Brain.CurrentState?.StateEnter(scientistNPC.Brain, scientistNPC);
}
}
#endregion Methods
#region Classes
class RoamState : BaseAIBrain.BasicAIState
{
ScientistNPC scientistNPC;
Vector3 roamPosition;
float roamRadius;
internal RoamState(Vector3 roamPosition, float roamRadius, ScientistNPC scientistNPC) : base(AIState.Roam)
{
this.roamPosition = roamPosition;
this.roamRadius = roamRadius;
this.scientistNPC = scientistNPC;
}
public override StateStatus StateThink(float delta, BaseAIBrain brain, BaseEntity entity)
{
base.StateThink(delta, brain, entity);
float distanceFromPatrolCenter = Vector3.Distance(scientistNPC.transform.position, roamPosition);
if (distanceFromPatrolCenter > roamRadius)
{
Vector3 roamPositionOnNavmesh = GetPositionOnNavmesh(roamPosition, 3);
NpcRoamToPosition(roamPositionOnNavmesh);
}
else if (!brain.Navigator.Moving)
{
Vector3 randomRoamPosition = GetRandomRoamPosition(roamPosition);
randomRoamPosition = GetPositionOnNavmesh(randomRoamPosition, 3);
NpcRoamToPosition(randomRoamPosition);
}
return StateStatus.Running;
}
Vector3 GetRandomRoamPosition(Vector3 source)
{
Vector2 vector2 = UnityEngine.Random.insideUnitCircle * roamRadius;
return source + new Vector3(vector2.x, 0f, vector2.y);
}
void NpcRoamToPosition(Vector3 position)
{
Vector3 currentDestination = scientistNPC.Brain.Navigator.Destination;
if (Vector3.Distance(currentDestination, position) < 0.1f)
return;
scientistNPC.Brain.Navigator.SetDestination(position, BaseNavigator.NavigationSpeed.Slow);
}
Vector3 GetPositionOnNavmesh(Vector3 position, float radius)
{
NavMeshHit navMeshHit;
if (NavMesh.SamplePosition(position, out navMeshHit, radius, scientistNPC.NavAgent.areaMask))
{
NavMeshPath path = new NavMeshPath();
if (NavMesh.CalculatePath(scientistNPC.transform.position, navMeshHit.position, scientistNPC.NavAgent.areaMask, path))
{
if (path.status == NavMeshPathStatus.PathComplete)
return navMeshHit.position;
else
return path.corners.Last();
}
}
return position;
}
}
static class LootManager
{
internal static void InitialLootManagerUpdate()
{
LootPrefabController.FindPrefabs();
}
internal static void UpdateScareCrowLoot(ItemContainer itemContainer)
{
if (itemContainer == null)
return;
ClearItemsContainer(itemContainer);
LootPrefabController.TryAddLootToScareCrow(itemContainer);
itemContainer.capacity = itemContainer.itemList.Count;
}
static void ClearItemsContainer(ItemContainer container)
{
for (int i = container.itemList.Count - 1; i >= 0; i--)
{
Item item = container.itemList[i];
item.RemoveFromContainer();
item.Remove();
}
}
class LootPrefabController
{
static LootPrefabController scareCrowLootController;
string prefabName;
LootContainer.LootSpawnSlot[] lootSpawnSlot;
LootSpawn lootDefinition;
int maxDefinitionsToSpawn;
int scrapAmount;
internal static void TryAddLootToScareCrow(ItemContainer itemContainer)
{
if (scareCrowLootController == null)
return;
TryFillContainerByPrefab(itemContainer);
}
internal static void FindPrefabs()
{
if (!string.IsNullOrEmpty(ins._config.scareCrowLootPrefabName))
TrySaveLootPrefab(ins._config.scareCrowLootPrefabName);
}
internal static void TrySaveLootPrefab(string prefabName)
{
GameObject gameObject = GameManager.server.FindPrefab(prefabName);
LootContainer lootContainer = gameObject.GetComponent<LootContainer>();
if (lootContainer != null)
{
SaveLootPrefabData(prefabName, lootContainer.LootSpawnSlots, lootContainer.scrapAmount, lootContainer.lootDefinition, lootContainer.maxDefinitionsToSpawn);
return;
}
global::HumanNPC humanNPC = gameObject.GetComponent<global::HumanNPC>();
if (humanNPC != null && humanNPC.LootSpawnSlots.Length > 0)
{
SaveLootPrefabData(prefabName, humanNPC.LootSpawnSlots, 0);
return;
}
global::ScarecrowNPC scarecrowNPC = gameObject.GetComponent<global::ScarecrowNPC>();
if (scarecrowNPC != null && scarecrowNPC.LootSpawnSlots.Length > 0)
{
SaveLootPrefabData(prefabName, scarecrowNPC.LootSpawnSlots, 0);
return;
}
}
internal static void SaveLootPrefabData(string prefabName, LootContainer.LootSpawnSlot[] lootSpawnSlot, int scrapAmount, LootSpawn lootDefinition = null, int maxDefinitionsToSpawn = 0)
{
LootPrefabController lootPrefabData = new LootPrefabController
{
prefabName = prefabName,
lootSpawnSlot = lootSpawnSlot,
lootDefinition = lootDefinition,
maxDefinitionsToSpawn = maxDefinitionsToSpawn,
scrapAmount = scrapAmount
};
scareCrowLootController = lootPrefabData;
}
internal static void TryFillContainerByPrefab(ItemContainer itemContainer)
{
LootPrefabController lootPrefabData = scareCrowLootController;
lootPrefabData.SpawnPrefabLootInCrate(itemContainer);
}
void SpawnPrefabLootInCrate(ItemContainer itemContainer)
{
if (lootSpawnSlot != null && lootSpawnSlot.Length > 0)
{
foreach (LootContainer.LootSpawnSlot lootSpawnSlot in lootSpawnSlot)
for (int j = 0; j < lootSpawnSlot.numberToSpawn; j++)
if (UnityEngine.Random.Range(0f, 1f) <= lootSpawnSlot.probability)
lootSpawnSlot.definition.SpawnIntoContainer(itemContainer);
}
else if (lootDefinition != null)
{
for (int i = 0; i < maxDefinitionsToSpawn; i++)
lootDefinition.SpawnIntoContainer(itemContainer);
}
GenerateScrap(itemContainer);
}
void GenerateScrap(ItemContainer itemContainer)
{
if (scrapAmount <= 0)
return;
Item item = ItemManager.CreateByName("scrap", scrapAmount, 0);
if (item == null)
return;
if (!item.MoveToContainer(itemContainer))
item.Remove();
}
}
}
#endregion Classes
#region Config
private PluginConfig _config;
protected override void LoadDefaultConfig()
{
_config = PluginConfig.DefaultConfig();
}
protected override void LoadConfig()
{
base.LoadConfig();
_config = Config.ReadObject<PluginConfig>();
Config.WriteObject(_config, true);
}
protected override void SaveConfig()
{
Config.WriteObject(_config);
}
public class CustomNpcWearConfig
{
[JsonProperty("Npc ShortPrefabName")] public string npcShortPrefabName { get; set; }
[JsonProperty("Replace clothes for this preset?")] public bool enable { get; set; }
[JsonProperty("Random Clothing presets")] public List<WearConfig> wearConfigs { get; set; }
}
public class WearConfig
{
[JsonProperty("List of items:")] public HashSet<WearItemConfig> wearItems { get; set; }
}
public class WearItemConfig
{
[JsonProperty("ShortName")] public string shortName { get; set; }
[JsonProperty("skin (0 - default)")] public ulong skinID { get; set; }
}
private class PluginConfig
{
[JsonProperty("Version")] public VersionNumber version { get; set; }
[JsonProperty("NPC patrol Radius (-1 - Do not change the behavior; 0 - Do not change the radius)")] public float npcPatrolRadius { get; set; }
[JsonProperty("Scarecrow patrol Radius (-1 - Do not change the behavior)")] public float scarecrowPatrolRadius { get; set; }
[JsonProperty("Replace scarecrow loot with prefab (Empty - do not replace)")] public string scareCrowLootPrefabName { get; set; }
[JsonProperty("Scarecrow Health Scale")] public float scareCrowHealthScale { get; set; }
[JsonProperty("Configuring the replacement of NPC clothes")] public HashSet<CustomNpcWearConfig> customNpcWearConfigs { get; set; }
[JsonProperty("Configuring the replacement of Scarecrow clothes")] public List<WearConfig> scarecrowWearConfigs { get; set; }
public static PluginConfig DefaultConfig()
{
PluginConfig pluginConfig = new PluginConfig()
{
version = new VersionNumber(1, 0, 6),
npcPatrolRadius = 20,
scarecrowPatrolRadius = 0,
scareCrowHealthScale = 0.5f,
scareCrowLootPrefabName = "assets/rust.ai/agents/npcplayer/humannpc/scientist/scientistnpc_roam.prefab",
customNpcWearConfigs = new HashSet<CustomNpcWearConfig>
{
new CustomNpcWearConfig
{
npcShortPrefabName = "scientistnpc_roam",
enable = true,
wearConfigs = new List<WearConfig>
{
new WearConfig
{
wearItems = new HashSet<WearItemConfig>
{
new WearItemConfig
{
shortName = "jumpsuit.suit.blue",
skinID = 0,
},
new WearItemConfig
{
shortName = "shoes.boots",
skinID = 0,
},
new WearItemConfig
{
shortName = "hat.beenie",
skinID = 0,
},
}
},
new WearConfig
{
wearItems = new HashSet<WearItemConfig>
{
new WearItemConfig
{
shortName = "jumpsuit.suit",
skinID = 0,
},
new WearItemConfig
{
shortName = "shoes.boots",
skinID = 0,
},
new WearItemConfig
{
shortName = "mask.bandana",
skinID = 0,
},
new WearItemConfig
{
shortName = "hat.cap",
skinID = 0,
},
}
},
new WearConfig
{
wearItems = new HashSet<WearItemConfig>
{
new WearItemConfig
{
shortName = "halloween.surgeonsuit",
skinID = 0,
}
}
},
new WearConfig
{
wearItems = new HashSet<WearItemConfig>
{
new WearItemConfig
{
shortName = "halloween.surgeonsuit",
skinID = 0,
}
}
},
new WearConfig
{
wearItems = new HashSet<WearItemConfig>
{
new WearItemConfig
{
shortName = "hazmatsuit.arcticsuit",
skinID = 0,
}
}
},
new WearConfig
{
wearItems = new HashSet<WearItemConfig>
{
new WearItemConfig
{
shortName = "hazmatsuit_scientist_nvgm",
skinID = 0,
}
}
},
new WearConfig
{
wearItems = new HashSet<WearItemConfig>
{
new WearItemConfig
{
shortName = "hazmatsuit_scientist_peacekeeper",
skinID = 0,
}
}
},
new WearConfig
{
wearItems = new HashSet<WearItemConfig>
{
new WearItemConfig
{
shortName = "hazmatsuit_scientist_arctic",
skinID = 0,
}
}
},
new WearConfig
{
wearItems = new HashSet<WearItemConfig>
{
new WearItemConfig
{
shortName = "attire.banditguard",
skinID = 0,
}
}
},
new WearConfig
{
wearItems = new HashSet<WearItemConfig>
{
new WearItemConfig
{
shortName = "hazmatsuit.nomadsuit",
skinID = 0,
}
}
},
new WearConfig
{
wearItems = new HashSet<WearItemConfig>
{
new WearItemConfig
{
shortName = "hazmatsuit.lumberjack",
skinID = 0,
}
}
},
new WearConfig
{
wearItems = new HashSet<WearItemConfig>
{
new WearItemConfig
{
shortName = "hazmatsuit.diver",
skinID = 0,
}
}
}
}
}
},
scarecrowWearConfigs = new List<WearConfig>
{
new WearConfig
{
wearItems = new HashSet<WearItemConfig>
{
new WearItemConfig
{
shortName = "frankensteins.monster.01.head",
skinID = 0,
},
new WearItemConfig
{
shortName = "frankensteins.monster.01.torso",
skinID = 0,
},
new WearItemConfig
{
shortName = "frankensteins.monster.01.legs",
skinID = 0,
}
}
},
new WearConfig
{
wearItems = new HashSet<WearItemConfig>
{
new WearItemConfig
{
shortName = "frankensteins.monster.02.head",
skinID = 0,
},
new WearItemConfig
{
shortName = "frankensteins.monster.02.torso",
skinID = 0,
},
new WearItemConfig
{
shortName = "frankensteins.monster.02.legs",
skinID = 0,
}
}
},
new WearConfig
{
wearItems = new HashSet<WearItemConfig>
{
new WearItemConfig
{
shortName = "frankensteins.monster.03.head",
skinID = 0,
},
new WearItemConfig
{
shortName = "frankensteins.monster.03.torso",
skinID = 0,
},
new WearItemConfig
{
shortName = "frankensteins.monster.03.legs",
skinID = 0,
}
}
}
}
};
return pluginConfig;
}
}
#endregion Config
}
}
namespace Oxide.Plugins.NpcMovingControlExtensionMethods
{
public static class ExtensionMethods
{
public static TSource Last<TSource>(this IList<TSource> source) => source[source.Count - 1];
public static void ClearItemsContainer(this ItemContainer container)
{
for (int i = container.itemList.Count - 1; i >= 0; i--)
{
Item item = container.itemList[i];
item.RemoveFromContainer();
item.Remove();
}
}
public static TSource FirstOrDefault<TSource>(this IEnumerable<TSource> source, Func<TSource, bool> predicate)
{
using (var enumerator = source.GetEnumerator()) while (enumerator.MoveNext()) if (predicate(enumerator.Current)) return enumerator.Current;
return default(TSource);
}
public static HashSet<T> OfType<T>(this IEnumerable<BaseNetworkable> source)
{
HashSet<T> result = new HashSet<T>();
using (var enumerator = source.GetEnumerator()) while (enumerator.MoveNext()) if (enumerator.Current is T) result.Add((T)(object)enumerator.Current);
return result;
}
}
}