C#:
// Все права принадлежат дискорд сообществу https://discord.gg/VgNHPpNrz6
using Facepunch;
using System;
using System.Linq;
using System.Globalization;
using System.Collections.Generic;
using Oxide.Core;
using Oxide.Core.Plugins;
using UnityEngine;
using UnityEngine.AI;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using Oxide.Game.Rust.Cui;
using System.Reflection;
using HarmonyLib;
namespace Oxide.Plugins {
// For previous version changes, see end of file.//To do
// Make info show more info - Ui maybe? Proximity based?
// Map markers option.// Make melee npcs retreat if player is mounted.// Detect when target is dead and loot corpse.// Register permission for every profile created + ignore players with that permission @Venedas.// Add glowing eyes optional
// Offer GUIAnnouncements for spawn announcement.// Make eokas great again.// Spawn X profile after Y kills of Z profile
// Add back in move/spawn/kill commands.// Work on grenade aim where target is higher than npc.// Make grenade and syringe limited to the number in the kit.////////////////
// Option to re-read data + config from file, without reloading profiles/plugin.// Make data prefix + reload option in UI.// Make rocket npcs fire smoke from time to time, if LOS is lost.// Add Night_Profile option.// Add Only_throw_In_Building priv
// Parachuters flying away at heli kill profile - can't replicate
// Dont_Spawn_For_X_Hours_After_Wipe
// Changes in 1.3.2
// Scarecrow names fix.// Prevented "Parent Monument option from showing for non-custom profiles.// Added per-profile marker control options.// Marker_Enabled = false;
// Marker_Visible_When_NPCs_Are_Dead = true;
// Marker_Main_Colour = "#ffffff";
// Marker_Outer_Colour = "#000000";
// Marker_Opacity_Percent = 50;
// Marker_Radius_Percent = 20;
[Info("BotReSpawn", "Steenamaroo", "1.3.2", ResourceId = 0)]
[Description("Spawn tailored AI with kits at monuments, custom locations, or randomly.")]
class BotReSpawn : RustPlugin {
[PluginReference]
Plugin Convoy, NoSash, Kits, CustomLoot, RustRewards, XPerience;
int no_of_AI;
bool loaded;
static BotReSpawn bs;
static System.Random random = new System.Random();
public static string Get(ulong v) => RandomUsernames.Get((int)(v % 2147483647uL));
public static bool IsNight() {
if (bs.configData.Global.UseServerTime == true) return TOD_Sky.Instance.IsNight;
if (bs.configData.Global.NightStartHour > bs.configData.Global.DayStartHour) return TOD_Sky.Instance.Cycle.Hour >= bs.configData.Global.NightStartHour || TOD_Sky.Instance.Cycle.Hour < bs.configData.Global.DayStartHour;
else return TOD_Sky.Instance.Cycle.Hour >= bs.configData.Global.NightStartHour && TOD_Sky.Instance.Cycle.Hour < bs.configData.Global.DayStartHour;
}
int GetRand(int l, int h) => random.Next(l, h);
bool HasPermission(string id, string perm) => permission.UserHasPermission(id, perm);
const string permAllowed = "BotReSpawn.allowed";
const string huff = "assets/prefabs/npc/murderer/sound/breathing.prefab";
const string Parachute = "assets/prefabs/misc/parachute/parachute.prefab";
const string RocketExplosion = "assets/prefabs/weapons/rocketlauncher/effects/rocket_explosion.prefab";
const string LockedCrate = "assets/prefabs/deployable/chinooklockedcrate/codelockedhackablecrate.prefab";
ItemDefinition fuel = ItemManager.FindItemDefinition("lowgradefuel");
public List<ulong> HumanNPCs = new List<ulong>();
public List<Vector3> protect = new List<Vector3>();
public Dictionary<string, Profile> Profiles = new Dictionary<string, Profile>();
Dictionary<string, LootContainer> Containers = new Dictionary<string, LootContainer>() {
{
"Default NPC", null
}
}
;
LootContainer.LootSpawnSlot[] sc;
public Dictionary<ulong, BotData> NPCPlayers = new Dictionary<ulong, BotData>();
ItemDefinition scrap;
PlayerInventoryProperties[] scloadouts;
#region Setup + TakeDown
void Init() {
JsonConvert.DefaultSettings = () => new JsonSerializerSettings {
Formatting = Formatting.Indented, ReferenceLoopHandling = ReferenceLoopHandling.Ignore,
}
;
no_of_AI = 0;
}
void Loaded() {
ConVar.AI.npc_families_no_hurt = false;
lang.RegisterMessages(Messages, this);
permission.RegisterPermission(permAllowed, this);
}
bool Unloading = false;
void Unload() {
Unloading = true;
DestroySpawnGroups();
foreach (BasePlayer player in BasePlayer.activePlayerList) DestroyMenu(player, true);
}
void OnServerInitialized() {
bs = this;
scrap = ItemManager.FindItemDefinition("scrap");
var crow = (ScarecrowNPC)GameManager.server.FindPrefab("assets/prefabs/npc/scarecrow/scarecrow.prefab")?.GetComponent<ScarecrowNPC>();
if (crow != null) {
scloadouts = crow.loadouts;
sc = crow.LootSpawnSlots;
}
foreach (BasePlayer player in BaseNetworkable.serverEntities.OfType<BasePlayer>()) ProcessHumanNPC(player);
timer.Once(1f, () => {
if (NoSash) {
PrintWarning("NoSash plugin is installed");
PrintWarning("Target_Noobs option is now disabled.");
PrintWarning("BotReSpawn NPCs will target noob players.");
}
GetBiomePoints();
CheckMonuments(false);
LoadConfigVariables();
ImportFiles();
loaded = true;
SaveData();
SetupProfiles();
CheckKits();
SetupLootSources();
newsave = false;
timer.Once(0.1f, () => CreateSpawnGroups());
foreach (BradleyAPC apc in BaseNetworkable.serverEntities.OfType<BradleyAPC>()) SetupAPC(apc);
if (!configData.Global.Turret_Safe) Unsubscribe("CanBeTargeted");
}
);
// Remove in V1.1.0
BaseEntity.saveList.RemoveWhere(p => !p);
BaseEntity.saveList.RemoveWhere(p => p == null);
}
void ProcessHumanNPC(BasePlayer player) {
if (player == null || player.net?.connection != null || player.gameObject?.name == "BotReSpawn") return;
foreach (var comp in player.GetComponents<Component>()) if (comp?.GetType()?.Name == "HumanPlayer") {
HumanNPCs.Add(player.userID);
break;
}
}
#endregion
#region Setup Methods
void GetBiomePoints() {
var trees = BaseNetworkable.serverEntities.OfType<ResourceEntity>().Where(x => x is TreeEntity || x.ShortPrefabName.Contains("cactus"));
string biomename = "";
List<string> names = new List<string>();
int biome = -1;
Vector3 point = Vector3.zero;
foreach (var tree in trees.ToList()) {
biome = TerrainMeta.BiomeMap.GetBiomeMaxType(tree.transform.position, -1);
point = CalculateGroundPos(tree.transform.position + Vector3.forward / 2f, false, false);
if (point != Vector3.zero) {
biomename = $"Biome{Enum.GetName(typeof(TerrainBiome.Enum), biome)}";
if (BiomeSpawns.ContainsKey(biomename)) BiomeSpawns[biomename].Add(point);
else BiomeSpawns.Add(biomename, new List<Vector3> {
point
}
);
}
}
}
List<Vector3> OilRigs = new List<Vector3>();
void CheckMonuments(bool add) {
GameObject gobject;
Vector3 pos;
float rot;
foreach (var monumentInfo in TerrainMeta.Path.Monuments.OrderBy(x => x.displayPhrase.english)) {
var displayPhrase = monumentInfo.displayPhrase.english.Replace("\n", String.Empty);
if (displayPhrase.Contains("Water Well")) continue;
if (monumentInfo?.gameObject?.name != null) displayPhrase = ProcessName(monumentInfo.gameObject.name, displayPhrase);
gobject = monumentInfo.gameObject;
pos = gobject.transform.position;
rot = gobject.transform.eulerAngles.y;
int counter = 0;
if (displayPhrase != String.Empty) {
if (add) {
if (displayPhrase.Contains("Oil Rig")) OilRigs.Add(pos);
foreach (var entry in Profiles.Where(x => x.Key.Contains(displayPhrase) && (x.Key.Length == displayPhrase.Length + 2 || x.Key.Length == displayPhrase.Length + 3))) counter++;
AddProfile(gobject, $"{displayPhrase} {counter}", null, pos);
}
else {
foreach (var entry in GotMonuments.Where(x => x.Key.Contains(displayPhrase) && (x.Key.Length == displayPhrase.Length + 2 || x.Key.Length == displayPhrase.Length + 3))) counter++;
GotMonuments.Add($"{displayPhrase} {counter}", new Profile(ProfileType.Monument));
}
}
}
}
List<string> numerical = new List<string>() {
"mountain_", "power_sub_big_", "power_sub_small_", "ice_lake_"
}
;
string ProcessName(string name, string displayPhrase) {
foreach (var n in numerical) {
int num;
if (name.Length > n.Length && int.TryParse(name.Substring(name.IndexOf(n) + n.Length, 1), out num)) {
displayPhrase += name.Contains("sub_big_") ? "_Large" : name.Contains("sub_small_") ? "_Small" : "";
return displayPhrase + $"_{num}";
}
}
if (displayPhrase == "Harbor") displayPhrase += name.Contains("_1") ? "_Small" : "_Large";
else if (displayPhrase == "Fishing Village" || displayPhrase == "Wild Swamp") displayPhrase += name.Contains("_a") ? "_A" : name.Contains("_b") ? "_B" : "_C";
return displayPhrase;
}
void ImportFiles() {
storedData = Interface.Oxide.DataFileSystem.ReadObject<StoredData>($"BotReSpawn/{configData.DataPrefix}-CustomProfiles");
defaultData = Interface.Oxide.DataFileSystem.ReadObject<DefaultData>($"BotReSpawn/{configData.DataPrefix}-DefaultProfiles");
if (!configData.Global.Allow_Oilrigs) defaultData.Monuments = defaultData.Monuments.Where(x => !x.Key.Contains("Oil Rig")).ToDictionary(pair => pair.Key, pair => pair.Value);
spawnsData = Interface.Oxide.DataFileSystem.ReadObject<SpawnsData>($"BotReSpawn/{configData.DataPrefix}-SpawnsData");
templateData = Interface.Oxide.DataFileSystem.ReadObject<TemplateData>("BotReSpawn/TemplateData");
}
Dictionary<string, List<Vector3>> BiomeSpawns = new Dictionary<string, List<Vector3>>() {
{
"BiomeArid", new List<Vector3>()
}
, {
"BiomeTemperate", new List<Vector3>()
}
, {
"BiomeTundra", new List<Vector3>()
}
, {
"BiomeArctic", new List<Vector3>()
}
}
;
private void SetupProfiles() {
CheckMonuments(true);
foreach (var biome in defaultData.Biomes) AddProfile(new GameObject(), biome.Key, biome.Value, new Vector3());
foreach (var e in defaultData.Events) AddProfile(new GameObject(), e.Key, e.Value, new Vector3());
foreach (var profile in storedData.Profiles) AddData(profile.Key, profile.Value);
SaveData();
SetupSpawnsFile();
foreach (var profile in Profiles.Where(x => x.Value.type == ProfileType.Custom || x.Value.type == ProfileType.Monument)) if (profile.Value.Spawn.Kit.Count > 0 && Kits == null) PrintWarning(lang.GetMessage("nokits", this), profile.Key);
}
void CheckKits() {
ValidKits.Clear();
Kits?.Call("GetKitNames", new object[] {
ValidKits
}
);
var names = Kits?.Call("GetAllKits");
if (names != null) ValidKits.AddRange((string[])names);
ValidKits = ValidKits.Distinct().ToList();
foreach (var kit in ValidKits.ToList()) {
object checkKit = Kits?.CallHook("GetKitInfo", kit, true);
bool weaponInKit = false;
JObject kitContents = checkKit as JObject;
if (kitContents != null) {
JArray items = kitContents["items"] as JArray;
foreach (var weap in items) {
JObject item = weap as JObject;
if (Isweapon(item["itemid"].ToString(), null)) {
weaponInKit = true;
break;
}
}
}
if (!weaponInKit) ValidKits.Remove(kit);
}
if (Kits) foreach (var profile in Profiles) profile.Value.Spawn.Kit = profile.Value.Spawn.Kit.Where(x => ValidKits.Contains(x)).ToList();
ValidKits = ValidKits.OrderBy(x => x.ToString()).ToList();
}
void SetupLootSources() {
List<string> ignore = new List<string>();
ignore.AddRange("hidden,test,shelves,stocking,mission".Split(','));
Containers.Add("ScarecrowNPC", null);
Containers.Add("Random", null);
foreach (var entry in Resources.FindObjectsOfTypeAll<LootContainer>().Where(x => x != null && !x.isSpawned).ToList()) {
bool skip = false;
foreach (var i in ignore) if (entry.PrefabName.Contains(i)) skip = true;
if (skip) continue;
string name = GetLootName(entry.PrefabName, entry.ShortPrefabName);
if (Containers.ContainsKey(name)) continue;
Containers.Add(name, entry);
}
}
#endregion
#region Helpers
public string GetLootName(string name, string name2) => name.Contains("underwater_labs") ? "underwater_labs_" + name2 : name2;
public void PopulateLoot(LootContainer source, ItemContainer container) {
LootContainer.LootSpawnSlot[] lootSpawnSlots = source == null ? sc : source.LootSpawnSlots;
if (lootSpawnSlots.Length != 0) {
for (int i = 0; i < lootSpawnSlots.Length; i++) {
LootContainer.LootSpawnSlot lootSpawnSlot = lootSpawnSlots[i];
for (int j = 0; j < lootSpawnSlot.numberToSpawn; j++) if (UnityEngine.Random.Range(0f, 1f) <= lootSpawnSlot.probability) lootSpawnSlot.definition.SpawnIntoContainer(container);
}
}
else if (source?.lootDefinition != null) for (int k = 0; k < source.maxDefinitionsToSpawn; k++) source.lootDefinition.SpawnIntoContainer(container);
if (source?.SpawnType == LootContainer.spawnType.ROADSIDE || source?.SpawnType == LootContainer.spawnType.TOWN) foreach (Item item in container.itemList) if (item.hasCondition) item.condition = UnityEngine.Random.Range(item.info.condition.foundCondition.fractionMin, item.info.condition.foundCondition.fractionMax) * item.info.condition.max;
if (source != null && source.scrapAmount > 0) {
var sc = ItemManager.Create(scrap, source.scrapAmount, 0);
if (sc.MoveToContainer(container)) sc.Remove(0f);
}
}
static BasePlayer FindPlayerByName(string name) {
BasePlayer result = null;
foreach (BasePlayer current in BasePlayer.activePlayerList) {
if (current.displayName.Equals(name, StringComparison.OrdinalIgnoreCase) || current.UserIDString.Contains(name, CompareOptions.OrdinalIgnoreCase) || current.displayName.Contains(name, CompareOptions.OrdinalIgnoreCase)) result = current;
}
return result;
}
public bool Isweapon(string i, Item item) {
if (i != string.Empty) item = ItemManager.CreateByItemID(Convert.ToInt32(i), 1);
var held = item?.GetHeldEntity();
bool weapon = item?.info?.category == ItemCategory.Weapon || (held != null && (held as ThrownWeapon || held as BaseMelee || held as TorchWeapon));
if (i != string.Empty && item != null) item.Remove();
return weapon;
}
#endregion
#region Hooks
bool newsave = false;
void OnNewSave(string filename) => newsave = true;
object OnNpcKits(ulong userID) => NPCPlayers.ContainsKey(userID) ? true : (object)null;
object OnNpcDuck(global : : HumanNPC npc) => NPCPlayers.ContainsKey(npc.userID) ? true : (object)null;
private object CanEntityBeHostile(ScientistNPC npc) => npc != null && NPCPlayers.ContainsKey(npc.userID) ? true : (object)null;
private object CanBeTargeted(NPCPlayer npc, BaseEntity entity) => (npc != null && configData?.Global != null && configData.Global.Turret_Safe && NPCPlayers.ContainsKey(npc.userID)) ? false : (object)null;
//private object OnTurretTarget(NPCPlayer npc) => (npc != null && configData?.Global != null && configData.Global.Turret_Safe && NPCPlayers.ContainsKey(npc.userID)) ? false : (object)null; // Probably lighter? Make sure it works.private object CanBradleyApcTarget(BradleyAPC bradley, NPCPlayer npc)
{
if (Convoy && (bool)Convoy?.Call("IsConvoyVehicle", bradley)) return null;
return npc == null ? null : NPCPlayers.ContainsKey(npc.userID) ? !configData.Global.APC_Safe : (object)null;
}
private void OnEntitySpawned(BradleyAPC apc) => SetupAPC(apc);
void OnEntitySpawned(BasePlayer player) => NextTick(() => {
ProcessHumanNPC(player);
}
);
void SetupAPC(BradleyAPC apc) => apc.InvokeRepeating(() => UpdateTargetList(apc), 0f, 2f);
public void UpdateTargetList(BradleyAPC apc) {
List<BasePlayer> list = Pool.Get<List<BasePlayer>>();
Vis.Entities<BasePlayer>(apc.transform.position, apc.searchRange, list, 133120, QueryTriggerInteraction.Collide);
foreach (var player in list) {
if (!NPCPlayers.ContainsKey(player.userID) || !apc.VisibilityTest(player)) continue;
bool flag = false;
foreach (BradleyAPC.TargetInfo targetInfo in apc.targetList) {
if (targetInfo.entity == player) {
targetInfo.lastSeenTime = Time.time;
flag = true;
break;
}
}
if (flag) continue;
BradleyAPC.TargetInfo targetInfo1 = Pool.Get<BradleyAPC.TargetInfo>();
targetInfo1.Setup(player, Time.time);
apc.targetList.Add(targetInfo1);
}
list.Clear();
Pool.FreeUnmanaged<BasePlayer>(ref list);
}
object OnEntityTakeDamage(BaseHelicopter heli, HitInfo info) => info?.InitiatorPlayer != null && NPCPlayers.ContainsKey(info.InitiatorPlayer.userID) ? true : (object)null;
object OnEntityTakeDamage(ScientistNPC player, HitInfo info) {
if (player == null || info == null) return null;
BotData bData1;
NPCPlayers.TryGetValue(player.userID, out bData1);
if (bData1?.profile == null) return null;
if (info?.Initiator == player || info?.Initiator is BaseAnimalNPC) return true;
if (bData1.invincible || (bData1.profile.Other.Fire_Safe && info.damageTypes.GetMajorityDamageType() == Rust.DamageType.Heat)) return true;
if (info.InitiatorPlayer != null) {
if (bData1.profile.Behaviour.Friendly_Fire_Safe && NPCPlayers.ContainsKey(info.InitiatorPlayer.userID) && info.InitiatorPlayer != bData1.CurrentTarget) return true;
if (bData1.brain != null) {
if (bData1.inAir) {
if (bData1.profile.Other.Invincible_Whilst_Chuting) return true;
info.damageTypes.ScaleAll(10);
}
if (bData1.profile.Behaviour.Respect_Safe_Zones && player.InSafeZone()) return true;
if (info.InitiatorPlayer != bData1.CurrentTarget && Vector3.Distance(info.InitiatorPlayer.transform.position, player.transform.position) >= bData1.profile.Other.Immune_From_Damage_Beyond) return true;
if (info.InitiatorPlayer == bData1.CurrentTarget || bData1.Targets.ContainsKey(info.InitiatorPlayer) && (!player.limitNetworking && !player._limitedNetworking)) SetTarget(bData1, bData1.brain, info.InitiatorPlayer, true);
else if (bData1.WantsAttack(info.InitiatorPlayer, true)) {
SetTarget(bData1, bData1.brain, info.InitiatorPlayer, true);
if (configData.Global.NPCs_Assist_NPCs && info.InitiatorPlayer.net?.connection != null) {
var weapon = info?.Weapon as BaseProjectile;
List<BotData> bDatas = Pool.Get<List<BotData>>();
Vis.Components<BotData>(player.transform.position, (weapon != null && weapon.IsSilenced() ? 3 : Mathf.Max(50, bData1.profile.Behaviour.Assist_Sense_Range)), bDatas);
foreach (var bData in bDatas) {
if (bData.brain == null || bData == bData1) continue;
if (info.InitiatorPlayer != bData.CurrentTarget && Vector3.Distance(info.InitiatorPlayer.transform.position, player.transform.position) >= bData.profile.Other.Immune_From_Damage_Beyond) continue;
if (info.InitiatorPlayer == bData.CurrentTarget || bData.profilename == bData1.profilename || bData.Targets.ContainsKey(info.InitiatorPlayer)) //bData.WantsAttack(info.InitiatorPlayer, true))
{
timer.Once(Vector3.Distance(bData.transform.position, bData1.transform.position) / 5f, () => {
if (bData?.profile != null && bData.brain?.Senses != null && info.InitiatorPlayer != null && !info.InitiatorPlayer.IsDead()) SetTarget(bData, bData.brain, info.InitiatorPlayer, false);
}
);
}
//else
//{
// No LOS? SetDestination(...//}
}
bDatas.Clear();
Pool.FreeUnmanaged<BotData>(ref bDatas);
}
}
}
if (info != null && bData1.profile.Other.Die_Instantly_From_Headshot && info.isHeadshot) {
var weap = info?.Weapon?.GetItem()?.info?.shortname;
var weaps = bData1.profile.Other.Instant_Death_From_Headshot_Allowed_Weapons;
if (weaps.Count == 0 || (weap != null && weaps.Contains(weap))) {
if (!bData1.profile.Other.Require_Two_Headshots || Headshots.Contains(player.userID)) info.damageTypes.Set(0, player.health);
else Headshots.Add(player.userID);
return null;
}
}
}
if (configData.Global.APC_Safe && info?.Initiator is BradleyAPC) return true;
if (configData.Global.Pve_Safe) {
var att = info.Initiator?.ToString();
if (att == null && info.damageTypes.GetMajorityDamageType() == Rust.DamageType.Bullet) return true;
if (att == null || att.Contains("cactus") || att.Contains("barricade")) return true;
}
return null;
}
public List<ulong> Headshots = new List<ulong>();
void SetTarget(BotData bData, BaseAIBrain brain, BasePlayer target, bool delay) {
brain.Navigator.SetFacingDirectionEntity(target);
if (delay) {
timer.Once(random.Next(2, 15) / 10f, () => {
bData.Addto(bData.Players, target);
bData.Addto(bData.Targets, target);
bData.CurrentTarget = target;
}
);
}
else {
bData.Addto(bData.Players, target);
bData.Addto(bData.Targets, target);
bData.CurrentTarget = target;
}
}
object OnEntityKill(ScientistNPC npc) {
if (npc == null || npc.IsDestroyed) return null;
return !Unloading && npc?.gameObject?.name == "BotReSpawn" ? true : (object)null;
}
void OnPlayerDeath(BasePlayer player, HitInfo info) {
if (player == null) return;
if (HumanNPCs.Contains(player.userID)) HumanNPCs.Remove(player.userID);
if (player?.net?.connection != null) DestroyMenu(player, true);
ScientistNPC npc = player as ScientistNPC;
if (npc != null) OnEntityKill(npc, info, info?.InitiatorPlayer != null);
}
List<ulong> BSCrates = new List<ulong>();
void OnEntityKill(ScientistNPC npc, HitInfo info, bool killed) {
if (npc?.userID != null && NPCPlayers.ContainsKey(npc.userID) && !botInventories.ContainsKey(npc.userID)) {
botInventories.Add(npc.userID, null);
if (Headshots.Contains(npc.userID)) Headshots.Remove(npc.userID);
BotData bData;
NPCPlayers.TryGetValue(npc.userID, out bData);
if (bData == null || !Profiles.ContainsKey(bData?.profilename)) return;
if (!bData.temporary) timer.Once(Mathf.Max(1, bData.profile.Death.Respawn_Timer * 60), () => bData.sg?.SpawnBot(1, bData.profile.type == ProfileType.Biome || bData.profile.Spawn.ChangeCustomSpawnOnDeath ? null : bData.sp));
if (killed) {
if (!info.InitiatorPlayer.IsNpc) {
if (bData.profile.Death.RustRewardsValue != 0) {
var weapon = info?.Weapon?.GetItem()?.info?.shortname ?? info?.WeaponPrefab?.ShortPrefabName ?? "";
RustRewards?.Call("GiveRustReward", info.InitiatorPlayer, 0, bData.profile.Death.RustRewardsValue, npc, weapon, Vector3.Distance(info.InitiatorPlayer.transform.position, npc.transform.position), null);
}
if (bData.profile.Death.XPerienceValue > 0) XPerience?.Call("GiveXP", info.InitiatorPlayer, bData.profile.Death.XPerienceValue);
}
if (bData.profile.Death.Spawn_Hackable_Death_Crate_Percent > 0 && bData.profile.Death.Spawn_Hackable_Death_Crate_Percent > GetRand(1, 101) && npc.WaterFactor() < 0.1f) {
var pos = npc.transform.position;
timer.Once(2f, () => {
if (bData?.profile == null) return;
var Crate = (HackableLockedCrate)GameManager.server.CreateEntity(LockedCrate, pos + new Vector3(1, 2, 0), Quaternion.Euler(0, 0, 0));
Crate.Spawn();
BSCrates.Add(Crate.net.ID.Value);
Crate.hackSeconds = HackableLockedCrate.requiredHackSeconds - bData.profile.Death.Death_Crate_LockDuration * 60;
timer.Once(1.4f, () => {
if (Crate == null || bData?.profile == null) return;
if (CustomLoot && bData.profile.Death.Death_Crate_CustomLoot_Profile != string.Empty) {
if (Crate != null) ////Check
{
Crate.inventory.capacity = 36;
Crate.onlyAcceptCategory = ItemCategory.All;
Crate.SendNetworkUpdateImmediate();
Crate.inventory.Clear();
List<Item> loot = (List<Item>)CustomLoot?.Call("MakeLoot", bData.profile.Death.Death_Crate_CustomLoot_Profile);
if (loot != null) foreach (var item in loot) if (!item.MoveToContainer(Crate.inventory, -1, true)) item.Remove();
}
}
}
);
}
);
}
Interface.CallHook("OnBotReSpawnNPCKilled", npc, bData.profilename, bData.group, info);
}
Item activeItem = npc.GetActiveItem();
if (activeItem != null && bData.profile.Death.Weapon_Drop_Percent > 0 && bData.profile.Death.Weapon_Drop_Percent >= GetRand(1, 101)) {
var numb = GetRand(Mathf.Min(bData.profile.Death.Min_Weapon_Drop_Condition_Percent, bData.profile.Death.Max_Weapon_Drop_Condition_Percent), bData.profile.Death.Max_Weapon_Drop_Condition_Percent);
numb = Convert.ToInt16((numb / 100f) * activeItem.maxCondition);
activeItem.condition = numb;
activeItem.Drop(npc.eyes.position, new Vector3(), new Quaternion());
npc.svActiveItemID = new ItemId();
npc.SendNetworkUpdate(BasePlayer.NetworkQueue.Update);
}
ItemContainer[] source = {
npc.inventory.containerMain, npc.inventory.containerWear, npc.inventory.containerBelt
}
;
Inv botInv = new Inv() {
profile = bData.profile, name = npc.displayName,
}
;
botInventories[npc.userID] = botInv;
for (int i = 0; i < source.Length; i++) foreach (var item in source[i].itemList) {
botInv.inventory[i].Add(new InvContents {
ID = item.info.itemid, amount = item.amount, skinID = item.skin,
}
);
}
DeadNPCPlayerIds.Add(npc.userID, bData.profile.Other.Backpack_Duration * 60);
no_of_AI--;
}
}
#endregion
#region Events
List<ulong> CHCrates = new List<ulong>();
List<CH47Helicopter> helis = new List<CH47Helicopter>();
bool NearCH(HackableLockedCrate crate) {
helis.Clear();
Vis.Entities<CH47Helicopter>(crate.transform.position, 10f, helis);
return helis.Any();
}
List<ulong> ORCrates = new List<ulong>();
bool NearOR(HackableLockedCrate crate) {
foreach (var entry in OilRigs) if (Vector3.Distance(crate.transform.position, entry) < 100) return true;
return false;
}
bool ValidCrate(HackableLockedCrate crate) {
if (crate.OwnerID != 0) return configData.Global.Allow_HackableCrates_With_OwnerID;
if (CHCrates.Contains(crate.net.ID.Value)) return configData.Global.Allow_HackableCrates_From_CH47;
if (ORCrates.Contains(crate.net.ID.Value)) return configData.Global.Allow_HackableCrates_At_Oilrig;
if (crate.HasParent() || crate.transform?.parent != null) return configData.Global.Allow_Parented_HackedCrates;
return configData.Global.Allow_All_Other_HackedCrates;
}
void OnEntitySpawned(HackableLockedCrate crate) {
if (!loaded || crate?.net?.ID == null) return;
NextTick(() => {
if (crate?.net?.ID == null || !loaded || BSCrates.Contains(crate.net.ID.Value)) return;
if (Interface.CallHook("OnBotReSpawnCrateDropped", crate) != null) return;
if (NearCH(crate)) CHCrates.Add(crate.net.ID.Value);
if (NearOR(crate)) ORCrates.Add(crate.net.ID.Value);
var nearest = FindNearestDefault(crate.transform.position);
if (nearest != null && nearest.Other.Block_Event_LockedCrate_Spawn) return;
if (ValidCrate(crate)) DoEvent("LockedCrate_Spawn", crate.transform.position);
}
);
}
void OnCrateHack(HackableLockedCrate crate) {
NextTick(() => {
if (crate?.net?.ID == null || !loaded) return;
if (Interface.CallHook("OnBotReSpawnCrateHackBegin", crate) != null) return;
var nearest = FindNearestDefault(crate.transform.position);
if (nearest != null && nearest.Other.Block_Event_LockedCrate_HackStart) return;
if (ValidCrate(crate)) DoEvent("LockedCrate_HackStart", crate.transform.position);
}
);
}
void OnEntityDeath(BaseEntity entity) {
if (entity == null || entity.transform == null || entity is BasePlayer) return;
string prof = string.Empty;
if (entity is BradleyAPC) {
if (Interface.CallHook("OnBotReSpawnAPCKill", entity) != null) return;
var nearest = FindNearestDefault(entity.transform.position);
if (nearest != null && nearest.Other.Block_Event_APC_Kill) return;
prof = "APC_Kill";
}
else if (entity is PatrolHelicopter) {
if (Interface.CallHook("OnBotReSpawnPatrolHeliKill", entity) != null) return;
var nearest = FindNearestDefault(entity.transform.position);
if (nearest != null && nearest.Other.Block_Event_PatrolHeli_Kill) return;
prof = "PatrolHeli_Kill";
}
else if (entity is CH47Helicopter) {
if (Interface.CallHook("OnBotReSpawnCH47Kill", entity) != null) return;
var nearest = FindNearestDefault(entity.transform.position);
if (nearest != null && nearest.Other.Block_Event_CH47_Kill) return;
prof = "CH47_Kill";
}
if (prof != string.Empty) DoEvent(prof, entity.transform.position);
}
void DoEvent(string name, Vector3 pos) {
Profile profile = Profiles[name];
if (profile.Spawn.AutoSpawn == true && GetPop(profile) > 0) {
int quantity = GetPop(profile);
if (profile.Spawn.AutoSpawn == true && quantity > 0) {
profile.Other.Location = pos;
CreateTempSpawnGroup(pos, name, profile, string.Empty, quantity);
}
}
}
#endregion
#region SpawningHooks
void OnEntitySpawned(DroppedItemContainer container) {
NextTick(() => {
if (!loaded || container?.playerSteamID == null || container.IsDestroyed || container.playerSteamID == 0) return;
if (DeadNPCPlayerIds.ContainsKey(container.playerSteamID)) {
if (configData.Global.Remove_BackPacks_Percent >= GetRand(1, 101)) {
if (container != null && !container.IsDestroyed) container.Kill();
}
else {
container.CancelInvoke(container.RemoveMe);
timer.Once(DeadNPCPlayerIds[container.playerSteamID], () => {
if (container != null && !container.IsDestroyed) container?.Kill();
}
);
}
DeadNPCPlayerIds.Remove(container.playerSteamID);
}
}
);
}
void OnEntitySpawned(SupplySignal signal) {
if (!loaded || configData.Global.Ignore_Skinned_Supply_Grenades && signal.skinID != 0) return;
timer.Once(2.3f, () => {
if (!loaded || signal != null) SmokeGrenades.Add(new Vector3(signal.transform.position.x, 0, signal.transform.position.z));
}
);
}
void OnEntitySpawned(SupplyDrop drop) {
if (!loaded || (!drop.name.Contains("supply_drop") && !drop.name.Contains("sleigh/presentdrop"))) return;
if (Interface.CallHook("OnBotReSpawnAirdrop", drop) != null) return;
if (!configData.Global.Supply_Enabled) {
foreach (var location in SmokeGrenades.Where(location => Vector3.Distance(location, new Vector3(drop.transform.position.x, 0, drop.transform.position.z)) < 35f)) {
SmokeGrenades.Remove(location);
return;
}
}
Profile profile = null;
Profiles.TryGetValue("AirDrop", out profile);
if (profile != null) DoEvent("AirDrop", drop.transform.position);
}
//object CanPopulateLoot(ScientistNPC entity, NPCPlayerCorpse corpse) => botInventories.ContainsKey(corpse.playerSteamID) ? true : (object)null;
object CanPopulateLoot(ScientistNPC entity, NPCPlayerCorpse corpse) => corpse?.playerSteamID != null && !configData.Global.Allow_AlphaLoot && botInventories.ContainsKey(corpse.playerSteamID) ? true : (object)null;
void OnEntitySpawned(NPCPlayerCorpse corpse) {
if (!loaded || corpse == null || corpse.IsDestroyed) return;
Inv botInv = new Inv();
ulong id = corpse.playerSteamID;
timer.Once(0.1f, () => {
if (corpse == null || corpse.IsDestroyed || !botInventories.ContainsKey(id)) return;
botInv = botInventories[id];
if (botInv == null) return;
Profile profile = botInv.profile;
timer.Once(profile.Death.Corpse_Duration * 60, () => {
if (corpse != null && !corpse.IsDestroyed) corpse?.Kill();
}
);
if (!(profile.Death.Allow_Rust_Loot_Percent >= GetRand(1, 101))) corpse.containers[0].Clear();
else {
if (profile.Death.Rust_Loot_Source != "Default NPC") {
LootContainer container = null;
if (profile.Death.Rust_Loot_Source == "Random") container = Containers.ElementAt(random.Next(0, Containers.Count)).Value;
else Containers.TryGetValue(profile.Death.Rust_Loot_Source, out container);
corpse.containers[0].Clear();
//// Needs AL private void PopulateLootAPI(ItemContainer container, string name) or private List<Item>GetLoot(string name)
//if (AlphaLoot && profile.Death.Use_AlphaLoot)
// AlphaLoot.Call("PopulateLoot", profile.Death.Rust_Loot_Source == "ScarecrowNPC" ? "scarecrow" : container.ShortPrefabName, corpse.containers[0]);
//else
PopulateLoot(container, corpse.containers[0]);
}
foreach (var item in corpse.containers[0].itemList.ToList()) if ((configData.Global.Remove_KeyCard && item.info.shortname.Contains("keycard")) || (configData.Global.Remove_Frankenstein_Parts && item.info.shortname.Contains("frankensteins."))) item.Remove();
}
Item playerSkull = ItemManager.CreateByName("skull.human", 1);
playerSkull.name = string.Concat($"Skull of {botInv.name}");
ItemAmount SkullInfo = new ItemAmount() {
itemDef = playerSkull.info, amount = 1, startAmount = 1
}
;
var dispenser = corpse.resourceDispenser;
if (dispenser != null) {
dispenser.containedItems.Add(SkullInfo);
dispenser.Initialize();
}
for (int i = 0; i < botInv.inventory.Length; i++) {
if (i == 1) continue;
foreach (var item in botInv.inventory[i]) {
var giveItem = ItemManager.CreateByItemID(item.ID, item.amount, item.skinID);
if (!giveItem.MoveToContainer(corpse.containers[i], -1, true)) giveItem.Remove();
}
}
timer.Once(5f, () => botInventories?.Remove(id));
timer.Once(1f, () => {
if (profile != null) corpse?.ResetRemovalTime(profile.Death.Corpse_Duration * 60);
if (corpse != null) foreach (var container in corpse.containers) container.canAcceptItem = new Func<Item, int, bool>(CanAcceptItem);
}
);
if (profile.Spawn.Give_Scarecrow_Loadout || profile.Death.Wipe_Belt_Percent >= GetRand(1, 101)) corpse.containers[2].Clear();
if (profile.Spawn.Give_Scarecrow_Loadout || profile.Death.Wipe_Clothing_Percent >= GetRand(1, 101)) corpse.containers[1].Clear();
ItemManager.DoRemoves();
corpse.containers = new ItemContainer[] {
corpse.containers[0], new ItemContainer(), new ItemContainer(), corpse.containers[1], corpse.containers[2]
}
;
foreach (var item in corpse.containers[3].itemList.ToList()) {
if (item.info.shortname.Contains("frankensteins.")) {
corpse.containers[0].capacity += 1;
item.MoveToContainer(corpse.containers[0], corpse.containers[0].capacity - 1);
}
}
corpse._playerName = botInv.name;
corpse.lootPanelName = botInv.name;
}
);
}
#endregion
public bool CanAcceptItem(Item item, int i) => false;
#region WeaponSwitching
void SelectWeapon(ScientistNPC npc, BotData bData, BaseAIBrain brain) {
if (npc == null) {
TidyUp(npc, bData, $"Selectweapon - NPC is null - {bData.profilename}");
return;
}
if (bData == null || bData.throwing || bData.healing || brain?.Senses == null || npc?.inventory?.containerBelt == null) return;
if (bData.CurrentTarget == null || !bData.Targets.ContainsKey(bData.CurrentTarget) || !brain.Senses.Memory.IsLOS(bData.CurrentTarget)) bData.GetNearest();
if (bData.CurrentTarget == null && bData.gc != null) {
UpdateActiveItem(npc, bData.gc, bData);
return;
}
Range enemyrange = bData.CurrentTarget == null ? TargetRange(IsNight() ? 1 : 41) : TargetRange(Vector3.Distance(npc.transform.position, bData.CurrentTarget.transform.position));
Range bestrange = BestRange(bData, enemyrange);
bData.canFire = !(configData.Global.Limit_ShortRange_Weapon_Use && bestrange == Range.Close && enemyrange == Range.Long);
if (bData.profile.Behaviour.Dont_Fire_Beyond_Distance > 0 && bData.CurrentTarget != null && Vector3.Distance(npc.transform.position, bData.CurrentTarget.transform.position) > bData.profile.Behaviour.Dont_Fire_Beyond_Distance) bData.canFire = false;
if (bestrange == bData.currentRange) {
var held = npc.GetHeldEntity();
if (held != bData.gc) {
SetLights(bData, held);
return;
}
}
bData.currentRange = bestrange;
UpdateActiveItem(npc, bData.Weaps[bestrange].GetRandom(), bData);
}
void SetLights(BotData bData, HeldEntity held) {
if (held.ShortPrefabName.Contains("flashlight")) {
held.SetLightsOn(bData.profile.Behaviour.AlwaysUseLights ? true : IsNight());
}
else if (held is TorchWeapon) {
bData.torch = held as TorchWeapon;
bData.torch.SetIsOn(bData.profile.Behaviour.AlwaysUseLights ? true : IsNight());
bData.torch.CancelInvoke(new Action(bData.torch.UseFuel));
}
else {
bData.gun = held as BaseProjectile;
if (bData.gun != null) bData.gun.SetLightsOn(bData.profile.Behaviour.AlwaysUseLights ? true : IsNight());
}
if (bData.hasHeadLamp) HeadLampToggle(bData.npc, bData.profile.Behaviour.AlwaysUseLights ? true : IsNight());
}
void UpdateActiveItem(ScientistNPC npc, HeldEntity held, BotData bData) {
if (held?.GetItem() == null) return;
if (npc?.inventory == null || npc.IsDead() || bData == null) {
npc?.CancelInvoke("SelectWeapon");
return;
}
var activeItem = npc.GetHeldEntity();
if (activeItem == held) return;
npc.svActiveItemID = new ItemId();
if (activeItem != null) activeItem.SetHeld(false);
npc.svActiveItemID = held.GetItem().uid;
npc.SendNetworkUpdate(BasePlayer.NetworkQueue.Update);
SetRange(npc, held, bData);
if (held != null) held.SetHeld(true);
npc.inventory.UpdatedVisibleHolsteredItems();
var pr = held as BaseProjectile;
if (pr?.primaryMagazine != null) {
pr.primaryMagazine.contents = pr.primaryMagazine.capacity;
return;
}
var cs = held as Chainsaw;
if (cs != null) {
if (cs.HasFlag(BaseEntity.Flags.On)) return;
cs.SetEngineStatus(true);
cs.SendNetworkUpdateImmediate(false);
}
var jh = held as Jackhammer;
if (jh != null) {
if (jh.HasFlag(BaseEntity.Flags.On)) return;
jh.SetEngineStatus(true);
jh.SendNetworkUpdateImmediate(false);
}
}
void SetRange(ScientistNPC npc, HeldEntity held, BotData bData) {
var weapon = held as AttackEntity;
if (bData != null && weapon != null) weapon.effectiveRange = bData.Weaps[Range.Melee].Contains(held) ? 2 : 410;
}
void HeadLampToggle(ScientistNPC npc, bool NewState) {
foreach (var item in npc.inventory.containerWear.itemList.Where(item => item.info.shortname.Equals("hat.miner") || item.info.shortname.Equals("hat.candle"))) {
if ((NewState && !item.IsOn()) || (!NewState && item.IsOn())) {
item.SwitchOnOff(NewState);
npc.inventory.ServerUpdate(0f);
break;
}
}
}
bool debug = false;
void TidyUp(ScientistNPC npc, BotData bData, string message) {
// ... [Code preview truncated due to length. Full source in attached file] ...