C#:
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using Oxide.Core;
using Oxide.Core.Plugins;
using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.AI;
using Oxide.Plugins.BetterNpcExtensionMethods;
namespace Oxide.Plugins {
[Info("BetterNpc", "KpucTaJl", "1.1.8")]
internal class BetterNpc : RustPlugin {
#region Config
private const bool En = true;
private PluginConfig _config;
protected override void LoadDefaultConfig() {
Puts("Creating a default config...");
_config = PluginConfig.DefaultConfig();
_config.PluginVersion = Version;
SaveConfig();
Puts("Creation of the default config completed!");
}
protected override void LoadConfig() {
base.LoadConfig();
_config = Config.ReadObject<PluginConfig>();
if (_config.PluginVersion < Version) UpdateConfigValues();
}
private void UpdateConfigValues() {
Puts("Config update detected! Updating config values...");
if (_config.PluginVersion < new VersionNumber(1, 0, 9)) {
_config.UnderwaterSpawnPoints = new HashSet<string> {
"Military Tunnel", "module_900x900_2way_moonpool", "moonpool_1200x1500_1way", "moonpool_1200x1500_2way", "moonpool_1200x1500_3way", "moonpool_1200x1800_ladder", "module_1200x1200_1way", "module_1200x1200_1way_ladder", "module_1200x1200_3way", "module_1200x1200_3way_ladder", "module_1200x1200_4way", "module_1200x1200_4way_ladder", "module_1200x1800_2way", "module_1200x600_2way_corridor", "module_1500x1500_4way_lshaped", "module_1500x1500_ladder", "module_1500x1800_2way_ladder", "module_1500x600_2way_corridor", "module_1800x1800_4way_lshaped", "module_2100x600_3way_corridor", "module_900x900_1way", "module_900x900_1way_ladder", "module_900x900_3way", "module_900x900_3way_ladder", "module_900x900_4way", "module_900x900_4way_ladder"
}
;
_config.SafeZoneRange = 150f;
}
if (_config.PluginVersion < new VersionNumber(1, 1, 2)) {
HashSet<string> names = new HashSet<string> {
"curve-ne-0", "curve-ne-1", "curve-nw-0", "curve-nw-1", "curve-se-0", "curve-se-1", "curve-sw-0", "curve-sw-1", "intersection", "intersection-e", "intersection-n", "intersection-s", "intersection-w", "station-sn-0", "station-sn-1", "station-sn-2", "station-sn-3", "station-we-0", "station-we-1", "station-we-2", "station-we-3", "straight-sn-0", "straight-sn-1", "straight-sn-2", "straight-sn-3", "straight-sn-4", "straight-sn-5", "straight-we-0", "straight-we-1", "straight-we-2", "straight-we-3", "straight-we-4", "straight-we-5"
}
;
foreach (string name in names) _config.UnderwaterSpawnPoints.Add(name);
}
if (_config.PluginVersion < new VersionNumber(1, 1, 7)) {
_config.OtherNpc = new HashSet<string> {
"NpcRaider", "RandomRaider"
}
;
}
if (_config.PluginVersion < new VersionNumber(1, 1, 8)) {
if (!_config.OtherNpc.Contains("56485621526987")) _config.OtherNpc.Add("56485621526987");
}
_config.PluginVersion = Version;
Puts("Config update completed!");
SaveConfig();
}
protected override void SaveConfig() => Config.WriteObject(_config);
public class ItemConfig {
[JsonProperty("ShortName")]
public string ShortName {
get;
set;
}
[JsonProperty(En ? "Minimum" : "Минимальное кол-во")]
public int MinAmount {
get;
set;
}
[JsonProperty(En ? "Maximum" : "Максимальное кол-во")]
public int MaxAmount {
get;
set;
}
[JsonProperty(En ? "Chance [0.0-100.0]" : "Шанс выпадения предмета [0.0-100.0]")]
public float Chance {
get;
set;
}
[JsonProperty(En ? "Is this a blueprint? [true/false]" : "Это чертеж? [true/false]")]
public bool IsBluePrint {
get;
set;
}
[JsonProperty("SkinID (0 - default)")]
public ulong SkinID {
get;
set;
}
[JsonProperty(En ? "Name (empty - default)" : "Название (empty - default)")]
public string Name {
get;
set;
}
}
public class LootTableConfig {
[JsonProperty(En ? "Minimum numbers of items" : "Минимальное кол-во элементов")]
public int Min {
get;
set;
}
[JsonProperty(En ? "Maximum numbers of items" : "Максимальное кол-во элементов")]
public int Max {
get;
set;
}
[JsonProperty(En ? "Use minimum and maximum values? [true/false]" : "Использовать минимальное и максимальное значение? [true/false]")]
public bool UseCount {
get;
set;
}
[JsonProperty(En ? "List of items" : "Список предметов")]
public List<ItemConfig> Items {
get;
set;
}
}
public class PrefabConfig {
[JsonProperty(En ? "Chance [0.0-100.0]" : "Шанс выпадения [0.0-100.0]")]
public float Chance {
get;
set;
}
[JsonProperty(En ? "The path to the prefab" : "Путь к prefab-у")]
public string PrefabDefinition {
get;
set;
}
}
public class PrefabLootTableConfig {
[JsonProperty(En ? "Minimum numbers of prefabs" : "Минимальное кол-во prefab-ов")]
public int Min {
get;
set;
}
[JsonProperty(En ? "Maximum numbers of prefabs" : "Максимальное кол-во prefab-ов")]
public int Max {
get;
set;
}
[JsonProperty(En ? "Use minimum and maximum values? [true/false]" : "Использовать минимальное и максимальное значение? [true/false]")]
public bool UseCount {
get;
set;
}
[JsonProperty(En ? "List of prefabs" : "Список prefab-ов")]
public List<PrefabConfig> Prefabs {
get;
set;
}
}
public class NpcBelt {
[JsonProperty("ShortName")]
public string ShortName {
get;
set;
}
[JsonProperty(En ? "Amount" : "Кол-во")]
public int Amount {
get;
set;
}
[JsonProperty("SkinID (0 - default)")]
public ulong SkinID {
get;
set;
}
[JsonProperty(En ? "Mods" : "Модификации на оружие")]
public List<string> Mods {
get;
set;
}
[JsonProperty(En ? "Ammo" : "Боеприпасы")]
public string Ammo {
get;
set;
}
}
public class NpcWear {
[JsonProperty("ShortName")]
public string ShortName {
get;
set;
}
[JsonProperty("SkinID (0 - default)")]
public ulong SkinID {
get;
set;
}
}
public class NpcEconomic {
[JsonProperty("Economics")]
public double Economics {
get;
set;
}
[JsonProperty(En ? "Server Rewards (minimum 1)" : "Server Rewards (минимум 1)")]
public int ServerRewards {
get;
set;
}
[JsonProperty(En ? "IQEconomic (minimum 1)" : "IQEconomic (минимум 1)")]
public int IQEconomic {
get;
set;
}
}
public class NpcConfig {
[JsonProperty(En ? "Names" : "Названия")]
public List<string> Names {
get;
set;
}
[JsonProperty(En ? "Health" : "Кол-во ХП")]
public float Health {
get;
set;
}
[JsonProperty(En ? "Roam Range" : "Дальность патрулирования местности")]
public float RoamRange {
get;
set;
}
[JsonProperty(En ? "Chase Range" : "Дальность погони за целью")]
public float ChaseRange {
get;
set;
}
[JsonProperty(En ? "Attack Range Multiplier" : "Множитель радиуса атаки")]
public float AttackRangeMultiplier {
get;
set;
}
[JsonProperty(En ? "Sense Range" : "Радиус обнаружения цели")]
public float SenseRange {
get;
set;
}
[JsonProperty(En ? "Target Memory Duration [sec.]" : "Длительность памяти цели [sec.]")]
public float MemoryDuration {
get;
set;
}
[JsonProperty(En ? "Scale damage" : "Множитель урона")]
public float DamageScale {
get;
set;
}
[JsonProperty(En ? "Aim Cone Scale" : "Множитель разброса")]
public float AimConeScale {
get;
set;
}
[JsonProperty(En ? "Detect the target only in the NPC's viewing vision cone? [true/false]" : "Обнаруживать цель только в углу обзора NPC? [true/false]")]
public bool CheckVisionCone {
get;
set;
}
[JsonProperty(En ? "Vision Cone" : "Угол обзора")]
public float VisionCone {
get;
set;
}
[JsonProperty(En ? "Speed" : "Скорость")]
public float Speed {
get;
set;
}
[JsonProperty(En ? "Minimum time of appearance after death (not used for Events) [sec.]" : "Минимальное время появления после смерти (не используется для Events) [sec.]")]
public float MinTime {
get;
set;
}
[JsonProperty(En ? "Maximum time of appearance after death (not used for Events) [sec.]" : "Максимальное время появления после смерти (не используется для Events) [sec.]")]
public float MaxTime {
get;
set;
}
[JsonProperty(En ? "Disable radio effects? [true/false]" : "Отключать эффекты рации? [true/false]")]
public bool DisableRadio {
get;
set;
}
[JsonProperty(En ? "Is this a stationary NPC? [true/false]" : "Это стационарный NPC? [true/false]")]
public bool Stationary {
get;
set;
}
[JsonProperty(En ? "Remove a corpse after death? (it is recommended to use the true value to improve performance) [true/false]" : "Удалять труп после смерти? (рекомендуется использовать значение true для повышения производительности) [true/false]")]
public bool IsRemoveCorpse {
get;
set;
}
[JsonProperty(En ? "Wear items" : "Одежда")]
public List<NpcWear> WearItems {
get;
set;
}
[JsonProperty(En ? "Belt items" : "Быстрые слоты")]
public List<NpcBelt> BeltItems {
get;
set;
}
[JsonProperty(En ? "Kits (it is recommended to use the previous 2 settings to improve performance)" : "Kits (рекомендуется использовать предыдущие 2 пункта настройки для повышения производительности)")]
public List<string> Kits {
get;
set;
}
}
public class PresetConfig {
[JsonProperty(En ? "Enabled? [true/false]" : "Включен? [true/false]")]
public bool Enabled {
get;
set;
}
[JsonProperty(En ? "Minimum numbers - Day" : "Минимальное кол-во днем")]
public int MinDay {
get;
set;
}
[JsonProperty(En ? "Maximum numbers - Day" : "Максимальное кол-во днем")]
public int MaxDay {
get;
set;
}
[JsonProperty(En ? "Minimum numbers - Night" : "Минимальное кол-во ночью")]
public int MinNight {
get;
set;
}
[JsonProperty(En ? "Maximum numbers - Night" : "Максимальное кол-во ночью")]
public int MaxNight {
get;
set;
}
[JsonProperty(En ? "NPCs setting" : "Настройки NPC")]
public NpcConfig Config {
get;
set;
}
[JsonProperty(En ? "The amount of economics that is given for killing the NPC" : "Кол-во экономики, которое выдается за убийство NPC")]
public NpcEconomic Economic {
get;
set;
}
[JsonProperty(En ? "Type of appearance (0 - random; 1 - own list) (not used for Road and Biome)" : "Тип появления (0 - рандомное; 1 - собственный список) (не используется для Road и Biome)")]
public int TypeSpawn {
get;
set;
}
[JsonProperty(En ? "Own list of locations (not used for Road and Biome)" : "Собственный список расположений (не используется для Road и Biome)")]
public List<string> OwnPositions {
get;
set;
}
[JsonProperty(En ? "The path to the crate that appears at the place of death (empty - not used)" : "Путь к ящику, который появляется на месте смерти (empty - not used)")]
public string CratePrefab {
get;
set;
}
[JsonProperty(En ? "Which loot table should the plugin use? (0 - default; 1 - own; 2 - AlphaLoot; 3 - CustomLoot; 4 - loot table of the Rust objects; 5 - combine the 1 and 4 methods)" : "Какую таблицу предметов необходимо использовать? (0 - стандартную; 1 - собственную; 2 - AlphaLoot; 3 - CustomLoot; 4 - таблица предметов объектов Rust; 5 - совместить 1 и 4 методы)")]
public int TypeLootTable {
get;
set;
}
[JsonProperty(En ? "Loot table from prefabs (if the loot table type is 4 or 5)" : "Таблица предметов из prefab-ов (если тип таблицы предметов - 4 или 5)")]
public PrefabLootTableConfig PrefabLootTable {
get;
set;
}
[JsonProperty(En ? "Own loot table (if the loot table type is 1 or 5)" : "Собственная таблица предметов (если тип таблицы предметов - 1 или 5)")]
public LootTableConfig OwnLootTable {
get;
set;
}
}
private class PluginConfig {
[JsonProperty(En ? "Start time of the day" : "Время начала дня")]
public string StartDayTime {
get;
set;
}
[JsonProperty(En ? "Start time of the night" : "Время начала ночи")]
public string StartNightTime {
get;
set;
}
[JsonProperty(En ? "Use the PVE mode of the plugin? (only for users PveMode plugin)" : "Использовать PVE режим работы плагина? (только для тех, кто использует плагин PveMode)")]
public bool Pve {
get;
set;
}
[JsonProperty(En ? "List of spawn points for the appearance of NPCs that are under water" : "Список точек для появления NPC, которые находятся под водой")]
public HashSet<string> UnderwaterSpawnPoints {
get;
set;
}
[JsonProperty(En ? "The distance from the center of the safe zone to the nearest place where the NPC appears [m.]" : "Расстояние от центра безопасной зоны до ближайшего места появления NPC [m.]")]
public float SafeZoneRange {
get;
set;
}
[JsonProperty(En ? "List of Npc types that should not be deleted" : "Список типов Npc, которые не должны удаляться")]
public HashSet<string> OtherNpc {
get;
set;
}
[JsonProperty(En ? "Configuration version" : "Версия конфигурации")]
public VersionNumber PluginVersion {
get;
set;
}
public static PluginConfig DefaultConfig() {
return new PluginConfig() {
StartDayTime = "8:00", StartNightTime = "20:00", Pve = false, UnderwaterSpawnPoints = new HashSet<string> {
"Military Tunnel", "module_900x900_2way_moonpool", "moonpool_1200x1500_1way", "moonpool_1200x1500_2way", "moonpool_1200x1500_3way", "moonpool_1200x1800_ladder", "module_1200x1200_1way", "module_1200x1200_1way_ladder", "module_1200x1200_3way", "module_1200x1200_3way_ladder", "module_1200x1200_4way", "module_1200x1200_4way_ladder", "module_1200x1800_2way", "module_1200x600_2way_corridor", "module_1500x1500_4way_lshaped", "module_1500x1500_ladder", "module_1500x1800_2way_ladder", "module_1500x600_2way_corridor", "module_1800x1800_4way_lshaped", "module_2100x600_3way_corridor", "module_900x900_1way", "module_900x900_1way_ladder", "module_900x900_3way", "module_900x900_3way_ladder", "module_900x900_4way", "module_900x900_4way_ladder", "curve-ne-0", "curve-ne-1", "curve-nw-0", "curve-nw-1", "curve-se-0", "curve-se-1", "curve-sw-0", "curve-sw-1", "intersection", "intersection-e", "intersection-n", "intersection-s", "intersection-w", "station-sn-0", "station-sn-1", "station-sn-2", "station-sn-3", "station-we-0", "station-we-1", "station-we-2", "station-we-3", "straight-sn-0", "straight-sn-1", "straight-sn-2", "straight-sn-3", "straight-sn-4", "straight-sn-5", "straight-we-0", "straight-we-1", "straight-we-2", "straight-we-3", "straight-we-4", "straight-we-5"
}
, SafeZoneRange = 150f, OtherNpc = new HashSet<string> {
"NpcRaider", "RandomRaider", "56485621526987"
}
, PluginVersion = new VersionNumber()
}
;
}
}
#endregion Config
#region Oxide Hooks
[PluginReference]
private readonly Plugin NpcSpawn, PveMode;
private static BetterNpc _ins;
private void Init() => _ins = this;
private void OnServerInitialized() {
if (!plugins.Exists("NpcSpawn")) {
PrintError("NpcSpawn plugin doesn`t exist! Please read the file ReadMe.txt");
NextTick(() => Interface.Oxide.UnloadPlugin(Name));
return;
}
_isDay = TOD_Sky.Instance.Cycle.Hour < TimeSpan.Parse(_config.StartNightTime).TotalHours && TOD_Sky.Instance.Cycle.Hour > TimeSpan.Parse(_config.StartDayTime).TotalHours;
_loadCoroutine = ServerMgr.Instance.StartCoroutine(LoadAllFiles());
}
private void Unload() {
if (_loadCoroutine != null) ServerMgr.Instance.StopCoroutine(_loadCoroutine);
if (_checkDayCoroutine != null) ServerMgr.Instance.StopCoroutine(_checkDayCoroutine);
foreach (ControllerSpawnPoint controller in _controllers) if (controller != null) UnityEngine.Object.Destroy(controller.gameObject);
_ins = null;
}
private void OnCorpsePopulate(ScientistNPC entity, NPCPlayerCorpse corpse) {
if (entity == null || corpse == null) return;
ControllerSpawnPoint controller = _controllers.FirstOrDefault(x => x.ActiveNpc.Any(y => y.Npc == entity));
if (controller != null) controller.DieNpc(entity, corpse);
}
private void OnEntitySpawned(NPCPlayer npc) {
ControllerSpawnPoint controller = _controllers.FirstOrDefault(s => s.RemoveOtherNpc && Vector3.Distance(s.transform.position, npc.transform.position) < (s.Size.x > s.Size.y ? s.Size.x : s.Size.y) * 2f && s.IsOtherNpc(npc));
if (controller != null) NextTick(() => {
if (npc.IsExists()) npc.Kill();
}
);
}
#endregion Oxide Hooks
#region Day or Night
private Coroutine _checkDayCoroutine = null;
private bool _isDay;
private IEnumerator CheckDay() {
while (true) {
if (_isDay) {
if (TOD_Sky.Instance.Cycle.Hour > TimeSpan.Parse(_config.StartNightTime).TotalHours || TOD_Sky.Instance.Cycle.Hour < TimeSpan.Parse(_config.StartDayTime).TotalHours) {
_isDay = false;
foreach (ControllerSpawnPoint controller in _controllers) controller.SetDay(_isDay);
}
}
else {
if (TOD_Sky.Instance.Cycle.Hour < TimeSpan.Parse(_config.StartNightTime).TotalHours && TOD_Sky.Instance.Cycle.Hour > TimeSpan.Parse(_config.StartDayTime).TotalHours) {
_isDay = true;
foreach (ControllerSpawnPoint controller in _controllers) controller.SetDay(_isDay);
}
}
yield return CoroutineEx.waitForSeconds(30f);
}
}
#endregion Day or Night
#region Controller
private readonly HashSet<ControllerSpawnPoint> _controllers = new HashSet<ControllerSpawnPoint>();
internal class ControllerSpawnPoint : FacepunchBehaviour {
internal string Name;
internal bool IsEvent;
internal bool RemoveOtherNpc;
internal Vector2 Size;
internal List<PresetConfig> Presets;
internal bool IsDay;
internal HashSet<ActiveScientistNpc> ActiveNpc = new HashSet<ActiveScientistNpc>();
private readonly HashSet<DeadScientistNpc> DeadNpc = new HashSet<DeadScientistNpc>();
private int GetAmountPreset(PresetConfig preset) => ActiveNpc.Where(x => x.Preset == preset).Count + DeadNpc.Where(x => x.Preset == preset).Count;
private int GetAmountPresetConfig(PresetConfig preset) => IsDay ? UnityEngine.Random.Range(preset.MinDay, preset.MaxDay) : UnityEngine.Random.Range(preset.MinNight, preset.MaxNight);
private void OnDestroy() {
CancelInvoke(ChangeDeadNpcTime);
foreach (ActiveScientistNpc activeScientistNpc in ActiveNpc.Where(x => x.Npc.IsExists())) activeScientistNpc.Npc.Kill();
}
internal void Init() {
if (RemoveOtherNpc) {
List<NPCPlayer> list = new List<NPCPlayer>();
Vis.Entities(transform.position, (Size.x > Size.y ? Size.x : Size.y) * 2f, list, 1 << 17);
foreach (NPCPlayer npc in list.Where(IsOtherNpc)) npc.Kill();
}
foreach (PresetConfig preset in Presets.Where(x => x.Enabled)) {
int amount = GetAmountPresetConfig(preset);
for (int i = 0; i < amount; i++) SpawnNpc(preset);
}
}
private void ChangeDeadNpcTime() {
foreach (DeadScientistNpc deadScientistNpc in DeadNpc) deadScientistNpc.TimeToSpawn--;
while (DeadNpc.Any(x => x.TimeToSpawn == 0)) {
DeadScientistNpc deadScientistNpc = DeadNpc.FirstOrDefault(x => x.TimeToSpawn == 0);
int amountPresetConfig = GetAmountPresetConfig(deadScientistNpc.Preset);
int amountPreset = GetAmountPreset(deadScientistNpc.Preset) - 1;
if (amountPresetConfig > amountPreset) SpawnNpc(deadScientistNpc.Preset);
DeadNpc.Remove(deadScientistNpc);
}
if (DeadNpc.Count == 0) CancelInvoke(ChangeDeadNpcTime);
}
internal void SetDay(bool day) {
IsDay = day;
foreach (PresetConfig preset in Presets) {
int amountPresetConfig = GetAmountPresetConfig(preset);
int amountPreset = GetAmountPreset(preset);
if (amountPresetConfig > amountPreset) {
int amount = amountPresetConfig - amountPreset;
for (int i = 0; i < amount; i++) SpawnNpc(preset);
}
else if (amountPresetConfig < amountPreset) {
int amount = amountPreset - amountPresetConfig;
for (int i = 0; i < amount; i++) KillNpc(preset);
}
}
}
private void SpawnNpc(PresetConfig preset) {
Vector3 pos = Vector3.zero;
int attempts = 0;
while (pos == Vector3.zero && attempts < 100) {
attempts++;
if (Name == "Arid" || Name == "Temperate" || Name == "Tundra" || Name == "Arctic") {
object point = _ins.NpcSpawn.Call("GetSpawnPoint", Name);
if (point is Vector3) pos = (Vector3)point;
}
else if (preset.TypeSpawn == 0) pos = GetRandomSpawnPos();
else pos = transform.TransformPoint(preset.OwnPositions.GetRandom().ToVector3());
if (ActiveNpc.Any(x => Vector3.Distance(x.Npc.transform.position, pos) < 5f)) pos = Vector3.zero;
if (!_ins._config.UnderwaterSpawnPoints.Contains(Name) && pos.y < -0.25f) pos = Vector3.zero;
if (pos != Vector3.zero && TriggerSafeZone.allSafeZones.Count > 0) {
TriggerSafeZone nearSafeZone = TriggerSafeZone.allSafeZones.Min(x => Vector3.Distance(pos, x.transform.position));
if (nearSafeZone != null && Vector3.Distance(pos, nearSafeZone.transform.position) < _ins._config.SafeZoneRange) pos = Vector3.zero;
}
}
if (pos != Vector3.zero) {
ScientistNPC npc = (ScientistNPC)_ins.NpcSpawn.Call("SpawnNpc", pos, GetObjectConfig(preset.Config));
if (npc != null) {
ActiveNpc.Add(new ActiveScientistNpc {
Preset = preset, Npc = npc
}
);
if (_ins._config.Pve && _ins.plugins.Exists("PveMode")) _ins.PveMode.Call("ScientistAddPveMode", npc);
}
}
}
private void KillNpc(PresetConfig preset) {
if (DeadNpc.Any(x => x.Preset == preset)) {
DeadScientistNpc deadScientistNpc = DeadNpc.Where(x => x.Preset == preset).ToList().GetRandom();
DeadNpc.Remove(deadScientistNpc);
}
else {
ActiveScientistNpc activeScientistNpc = ActiveNpc.Where(x => x.Preset == preset).ToList().GetRandom();
if (activeScientistNpc.Npc.IsExists()) activeScientistNpc.Npc.Kill();
ActiveNpc.Remove(activeScientistNpc);
}
}
internal void DieNpc(ScientistNPC npc, NPCPlayerCorpse corpse) {
ActiveScientistNpc activeScientistNpc = ActiveNpc.FirstOrDefault(x => x.Npc == npc);
PresetConfig preset = activeScientistNpc.Preset;
if (!string.IsNullOrEmpty(preset.CratePrefab)) {
BaseEntity entity = GameManager.server.CreateEntity(preset.CratePrefab, npc.transform.position, npc.transform.rotation);
if (entity == null) _ins.PrintWarning($"Unknown entity! ({preset.CratePrefab})");
else {
entity.enableSaving = false;
entity.Spawn();
}
}
if (!IsEvent) {
DeadNpc.Add(new DeadScientistNpc {
Preset = preset, TimeToSpawn = (int)UnityEngine.Random.Range(preset.Config.MinTime, preset.Config.MaxTime)
}
);
if (DeadNpc.Count == 1) InvokeRepeating(ChangeDeadNpcTime, 1f, 1f);
}
ActiveNpc.Remove(activeScientistNpc);
BasePlayer attacker = npc.lastAttacker as BasePlayer;
if (attacker.IsPlayer()) _ins.SendBalance(attacker.userID, preset.Economic);
_ins.NextTick(() => {
if (preset.TypeLootTable == 1 || preset.TypeLootTable == 4 || preset.TypeLootTable == 5) {
ItemContainer container = corpse.containers[0];
for (int i = container.itemList.Count - 1; i >= 0; i--) {
Item item = container.itemList[i];
item.RemoveFromContainer();
item.Remove();
}
if (preset.TypeLootTable == 4 || preset.TypeLootTable == 5) _ins.AddToContainerPrefab(container, preset.PrefabLootTable);
if (preset.TypeLootTable == 1 || preset.TypeLootTable == 5) _ins.AddToContainerItem(container, preset.OwnLootTable);
}
if (preset.Config.IsRemoveCorpse && corpse.IsExists()) corpse.Kill();
if (IsEvent && ActiveNpc.Count == 0) {
_ins._controllers.Remove(this);
Destroy(gameObject);
}
}
);
}
internal bool IsOtherNpc(NPCPlayer npc) {
if (!npc.IsExists()) return false;
if (_ins._config.OtherNpc.Contains(npc.GetType().Name)) return false;
if (_ins._config.OtherNpc.Contains(npc.displayName)) return false;
if (_ins._config.OtherNpc.Contains(npc.skinID.ToString())) return false;
if (npc.ShortPrefabName.Contains("scientistnpc_patrol") || npc.ShortPrefabName.Contains("scientistnpc_excavator") || npc.ShortPrefabName.Contains("scientistnpc_roamtethered") || npc.ShortPrefabName.Contains("scientistnpc_full") || npc.ShortPrefabName.Contains("scientistnpc_roam") || npc.ShortPrefabName.Contains("scientistnpc_oilrig") || npc.ShortPrefabName.Contains("npc_underwaterdweller") || npc.ShortPrefabName.Contains("npc_tunneldweller") || npc.ShortPrefabName.Contains("scarecrow")) return true;
return false;
}
private Vector3 GetRandomSpawnPos() {
RaycastHit raycastHit;
NavMeshHit navmeshHit;
int attempts = 0;
while (attempts < 10) {
attempts++;
Vector3 pos = transform.TransformPoint(new Vector3(UnityEngine.Random.Range(-Size.x, Size.x), 500f, UnityEngine.Random.Range(-Size.y, Size.y)));
if (!Physics.Raycast(pos, Vector3.down, out raycastHit, 500f, 1 << 16 | 1 << 23)) continue;
pos.y = raycastHit.point.y;
if (!NavMesh.SamplePosition(pos, out navmeshHit, 2f, 1)) continue;
pos = navmeshHit.position;
if (pos.y < -0.25f) continue;
return pos;
}
return Vector3.zero;
}
private JObject GetObjectConfig(NpcConfig config) {
HashSet<string> states = config.Stationary ? new HashSet<string> {
"IdleState", "CombatStationaryState"
}
: new HashSet<string> {
"RoamState", "ChaseState", "CombatState"
}
;
if (config.BeltItems.Any(x => x.ShortName == "rocket.launcher" || x.ShortName == "explosive.timed")) states.Add("RaidState");
return new JObject {
["Name"]
= config.Names.GetRandom(), ["WearItems"] = new JArray {
config.WearItems.Select(x => new JObject {
["ShortName"]
= x.ShortName, ["SkinID"] = x.SkinID
}
)
}
, ["BeltItems"] = new JArray {
config.BeltItems.Select(x => new JObject {
["ShortName"]
= x.ShortName, ["Amount"] = x.Amount, ["SkinID"] = x.SkinID, ["Mods"] = new JArray {
x.Mods
}
, ["Ammo"] = x.Ammo
}
)
}
, ["Kit"] = config.Kits.GetRandom(), ["Health"] = config.Health, ["RoamRange"] = config.RoamRange, ["ChaseRange"] = config.ChaseRange, ["DamageScale"] = config.DamageScale, ["TurretDamageScale"] = 1f, ["AimConeScale"] = config.AimConeScale, ["DisableRadio"] = config.DisableRadio, ["CanUseWeaponMounted"] = true, ["CanRunAwayWater"] = !_ins._config.UnderwaterSpawnPoints.Contains(Name), ["Speed"] = config.Speed, ["AreaMask"] = Name == "Large Oil Rig" || Name == "Oil Rig" || _ins._underwaterLabSpawnPoints.ContainsKey(Name) ? 25 : 1, ["AgentTypeID"] = Name == "Large Oil Rig" || Name == "Oil Rig" || _ins._underwaterLabSpawnPoints.ContainsKey(Name) ? 0 : -1372625422, ["HomePosition"] = string.Empty, ["States"] = new JArray {
states
}
, ["Sensory"] = new JObject {
["AttackRangeMultiplier"]
= config.AttackRangeMultiplier, ["SenseRange"] = config.SenseRange, ["MemoryDuration"] = config.MemoryDuration, ["CheckVisionCone"] = config.CheckVisionCone, ["VisionCone"] = config.VisionCone
}
}
;
}
internal class ActiveScientistNpc {
public PresetConfig Preset;
public ScientistNPC Npc;
}
internal class DeadScientistNpc {
public PresetConfig Preset;
public int TimeToSpawn;
}
}
#endregion Controller
#region Economy
[PluginReference]
private readonly Plugin Economics, ServerRewards, IQEconomic;
internal void SendBalance(ulong playerId, NpcEconomic economic) {
if (plugins.Exists("Economics") && economic.Economics > 0) Economics.Call("Deposit", playerId.ToString(), economic.Economics);
if (plugins.Exists("ServerRewards") && economic.ServerRewards > 0) ServerRewards.Call("AddPoints", playerId, economic.ServerRewards);
if (plugins.Exists("IQEconomic") && economic.IQEconomic > 0) IQEconomic.Call("API_SET_BALANCE", playerId, economic.IQEconomic);
}
#endregion Economy
#region Loot Spawn
private void AddToContainerPrefab(ItemContainer container, PrefabLootTableConfig lootTable) {
if (lootTable.UseCount) {
int count = UnityEngine.Random.Range(lootTable.Min, lootTable.Max + 1);
HashSet<string> prefabsInContainer = new HashSet<string>();
while (prefabsInContainer.Count < count) {
foreach (PrefabConfig prefab in lootTable.Prefabs) {
if (prefabsInContainer.Contains(prefab.PrefabDefinition)) continue;
if (UnityEngine.Random.Range(0.0f, 100.0f) <= prefab.Chance) {
if (_allLootSpawnSlots.ContainsKey(prefab.PrefabDefinition)) {
LootContainer.LootSpawnSlot[] lootSpawnSlots = _allLootSpawnSlots[prefab.PrefabDefinition];
foreach (LootContainer.LootSpawnSlot lootSpawnSlot in lootSpawnSlots) for (int j = 0; j < lootSpawnSlot.numberToSpawn; j++) if (UnityEngine.Random.Range(0f, 1f) <= lootSpawnSlot.probability) lootSpawnSlot.definition.SpawnIntoContainer(container);
}
else _allLootSpawn[prefab.PrefabDefinition].SpawnIntoContainer(container);
prefabsInContainer.Add(prefab.PrefabDefinition);
if (prefabsInContainer.Count == count) return;
}
}
}
}
else {
HashSet<string> prefabsInContainer = new HashSet<string>();
foreach (PrefabConfig prefab in lootTable.Prefabs) {
if (prefabsInContainer.Contains(prefab.PrefabDefinition)) continue;
if (UnityEngine.Random.Range(0.0f, 100.0f) <= prefab.Chance) {
if (_allLootSpawnSlots.ContainsKey(prefab.PrefabDefinition)) {
LootContainer.LootSpawnSlot[] lootSpawnSlots = _allLootSpawnSlots[prefab.PrefabDefinition];
foreach (LootContainer.LootSpawnSlot lootSpawnSlot in lootSpawnSlots) for (int j = 0; j < lootSpawnSlot.numberToSpawn; j++) if (UnityEngine.Random.Range(0f, 1f) <= lootSpawnSlot.probability) lootSpawnSlot.definition.SpawnIntoContainer(container);
}
else _allLootSpawn[prefab.PrefabDefinition].SpawnIntoContainer(container);
prefabsInContainer.Add(prefab.PrefabDefinition);
}
}
}
}
private void AddToContainerItem(ItemContainer container, LootTableConfig lootTable) {
if (lootTable.UseCount) {
int count = UnityEngine.Random.Range(lootTable.Min, lootTable.Max + 1);
HashSet<int> indexMove = new HashSet<int>();
while (indexMove.Count < count) {
foreach (ItemConfig item in lootTable.Items) {
if (indexMove.Contains(lootTable.Items.IndexOf(item))) continue;
if (UnityEngine.Random.Range(0.0f, 100.0f) <= item.Chance) {
Item newItem = item.IsBluePrint ? ItemManager.CreateByName("blueprintbase") : ItemManager.CreateByName(item.ShortName, UnityEngine.Random.Range(item.MinAmount, item.MaxAmount + 1), item.SkinID);
if (newItem == null) {
PrintWarning($"Failed to create item! ({item.ShortName})");
continue;
}
if (item.IsBluePrint) newItem.blueprintTarget = ItemManager.FindItemDefinition(item.ShortName).itemid;
if (!string.IsNullOrEmpty(item.Name)) newItem.name = item.Name;
if (container.capacity < container.itemList.Count + 1) container.capacity++;
if (!newItem.MoveToContainer(container)) newItem.Remove();
else {
indexMove.Add(lootTable.Items.IndexOf(item));
if (indexMove.Count == count) return;
}
}
}
}
}
else {
HashSet<int> indexMove = new HashSet<int>();
foreach (ItemConfig item in lootTable.Items) {
if (indexMove.Contains(lootTable.Items.IndexOf(item))) continue;
if (UnityEngine.Random.Range(0.0f, 100.0f) <= item.Chance) {
Item newItem = item.IsBluePrint ? ItemManager.CreateByName("blueprintbase") : ItemManager.CreateByName(item.ShortName, UnityEngine.Random.Range(item.MinAmount, item.MaxAmount + 1), item.SkinID);
if (newItem == null) {
PrintWarning($"Failed to create item! ({item.ShortName})");
continue;
}
if (item.IsBluePrint) newItem.blueprintTarget = ItemManager.FindItemDefinition(item.ShortName).itemid;
if (!string.IsNullOrEmpty(item.Name)) newItem.name = item.Name;
if (container.capacity < container.itemList.Count + 1) container.capacity++;
if (!newItem.MoveToContainer(container)) newItem.Remove();
else indexMove.Add(lootTable.Items.IndexOf(item));
}
}
}
}
private void CheckAllLootTables() {
foreach (KeyValuePair<string, MonumentSpawnPoint> dic in _monumentSpawnPoints) {
foreach (PresetConfig preset in dic.Value.Presets.Where(x => x.Enabled)) {
CheckLootTable(preset.OwnLootTable);
CheckPrefabLootTable(preset.PrefabLootTable);
}
Interface.Oxide.DataFileSystem.WriteObject($"BetterNpc/Monument/{dic.Key}", dic.Value);
}
foreach (KeyValuePair<string, MonumentSpawnPoint> dic in _underwaterLabSpawnPoints) {
foreach (PresetConfig preset in dic.Value.Presets.Where(x => x.Enabled)) {
CheckLootTable(preset.OwnLootTable);
CheckPrefabLootTable(preset.PrefabLootTable);
}
Interface.Oxide.DataFileSystem.WriteObject($"BetterNpc/Monument/Underwater Lab/{dic.Key}", dic.Value);
}
foreach (KeyValuePair<string, MonumentSpawnPoint> dic in _tunnelSpawnPoints) {
foreach (PresetConfig preset in dic.Value.Presets.Where(x => x.Enabled)) {
CheckLootTable(preset.OwnLootTable);
CheckPrefabLootTable(preset.PrefabLootTable);
}
Interface.Oxide.DataFileSystem.WriteObject($"BetterNpc/Monument/Tunnel/{dic.Key}", dic.Value);
}
foreach (KeyValuePair<string, CustomSpawnPoint> dic in _customSpawnPoints) {
foreach (PresetConfig preset in dic.Value.Presets.Where(x => x.Enabled)) {
CheckLootTable(preset.OwnLootTable);
CheckPrefabLootTable(preset.PrefabLootTable);
}
Interface.Oxide.DataFileSystem.WriteObject($"BetterNpc/Custom/{dic.Key}", dic.Value);
}
foreach (KeyValuePair<string, EventSpawnPoint> dic in _eventSpawnPoints) {
foreach (PresetConfig preset in dic.Value.Presets.Where(x => x.Enabled)) {
CheckLootTable(preset.OwnLootTable);
CheckPrefabLootTable(preset.PrefabLootTable);
}
Interface.Oxide.DataFileSystem.WriteObject($"BetterNpc/Event/{dic.Key}", dic.Value);
}
foreach (KeyValuePair<string, RoadSpawnPoint> dic in _roadSpawnPoints) {
foreach (PresetConfig preset in dic.Value.Presets.Where(x => x.Enabled)) {
CheckLootTable(preset.OwnLootTable);
CheckPrefabLootTable(preset.PrefabLootTable);
}
Interface.Oxide.DataFileSystem.WriteObject($"BetterNpc/Road/{dic.Key}", dic.Value);
}
foreach (KeyValuePair<string, BiomeSpawnPoint> dic in _biomeSpawnPoints) {
foreach (PresetConfig preset in dic.Value.Presets.Where(x => x.Enabled)) {
CheckLootTable(preset.OwnLootTable);
CheckPrefabLootTable(preset.PrefabLootTable);
}
Interface.Oxide.DataFileSystem.WriteObject($"BetterNpc/Biome/{dic.Key}", dic.Value);
}
}
private void CheckLootTable(LootTableConfig lootTable) {
lootTable.Items = lootTable.Items.OrderBy(x => x.Chance);
if (lootTable.Max > lootTable.Items.Count) lootTable.Max = lootTable.Items.Count;
if (lootTable.Min > lootTable.Max) lootTable.Min = lootTable.Max;
}
private void CheckPrefabLootTable(PrefabLootTableConfig lootTable) {
HashSet<PrefabConfig> prefabs = new HashSet<PrefabConfig>();
foreach (PrefabConfig prefabConfig in lootTable.Prefabs) {
if (prefabs.Any(x => x.PrefabDefinition == prefabConfig.PrefabDefinition)) PrintWarning($"Duplicate prefab removed from loot table! ({prefabConfig.PrefabDefinition})");
else {
GameObject gameObject = GameManager.server.FindPrefab(prefabConfig.PrefabDefinition);
global : : HumanNPC humanNpc = gameObject.GetComponent<global : : HumanNPC>();
ScarecrowNPC scarecrowNPC = gameObject.GetComponent<ScarecrowNPC>();
LootContainer lootContainer = gameObject.GetComponent<LootContainer>();
if (humanNpc != null && humanNpc.LootSpawnSlots.Length != 0) {
if (!_allLootSpawnSlots.ContainsKey(prefabConfig.PrefabDefinition)) _allLootSpawnSlots.Add(prefabConfig.PrefabDefinition, humanNpc.LootSpawnSlots);
prefabs.Add(prefabConfig);
}
else if (scarecrowNPC != null && scarecrowNPC.LootSpawnSlots.Length != 0) {
if (!_allLootSpawnSlots.ContainsKey(prefabConfig.PrefabDefinition)) _allLootSpawnSlots.Add(prefabConfig.PrefabDefinition, scarecrowNPC.LootSpawnSlots);
prefabs.Add(prefabConfig);
}
else if (lootContainer != null && lootContainer.LootSpawnSlots.Length != 0) {
if (!_allLootSpawnSlots.ContainsKey(prefabConfig.PrefabDefinition)) _allLootSpawnSlots.Add(prefabConfig.PrefabDefinition, lootContainer.LootSpawnSlots);
prefabs.Add(prefabConfig);
}
else if (lootContainer != null && lootContainer.lootDefinition != null) {
if (!_allLootSpawn.ContainsKey(prefabConfig.PrefabDefinition)) _allLootSpawn.Add(prefabConfig.PrefabDefinition, lootContainer.lootDefinition);
prefabs.Add(prefabConfig);
}
else PrintWarning($"Unknown prefab removed! ({prefabConfig.PrefabDefinition})");
}
}
lootTable.Prefabs = prefab
// ... [Code preview truncated due to length. Full source in attached file] ...