USER
using System;
using System.Collections.Generic;
using System.Linq;
using Facepunch;
using Newtonsoft.Json;
using Oxide.Core;
using Oxide.Core.Configuration;
using Rust;
using UnityEngine;
using Random = UnityEngine.Random;
using Time = UnityEngine.Time;
namespace Oxide.Plugins
{
[Info("Dungeons", "Marte6", "1.0.0")]
public class Dungeons : RustPlugin
{
private ConfigData _configData;
private DynamicConfigFile _dataFile;
private DungeonData _dungeonData;
private Timer _autoSpawnTimer;
private Timer _dungeonCheckTimer;
private Dictionary<DungeonTierConfig, string> _tierNameMap;
const float minDistanceBetweenDungeons = 50f;
#region Initialization
private void Init()
{
_tierNameMap = new Dictionary<DungeonTierConfig, string>
{
{ _configData.Tiers.Easy, "Easy" },
{ _configData.Tiers.Normal, "Normal" },
{ _configData.Tiers.Medium, "Medium" },
{ _configData.Tiers.Hard, "Hard" },
{ _configData.Tiers.Nightmare, "Nightmare" },
};
LoadData();
RemoveAllExistingDungeons();
if (_configData.AutoSpawn.EnableAutoSpawn)
{
StartAutoSpawnTimer();
}
StartDungeonCheckTimer();
}
private void LoadData()
{
_dataFile = Interface.Oxide.DataFileSystem.GetFile("Dungeons_Data");
try
{
_dungeonData = _dataFile.ReadObject<DungeonData>() ?? new DungeonData();
}
catch
{
_dungeonData = new DungeonData();
}
}
private void SaveData()
{
_dataFile.WriteObject(_dungeonData);
}
private void RemoveAllExistingDungeons()
{
foreach (var dungeon in _dungeonData.ActiveDungeons.ToList())
{
var basePortal = BaseNetworkable.serverEntities.Find(new NetworkableId(dungeon.PortalId)) as BasePortal;
if (basePortal != null)
{
RemoveDungeonEntities(dungeon);
basePortal.Kill(BaseNetworkable.DestroyMode.None);
}
RemoveDungeonMarkers(dungeon);
}
_dungeonData.ActiveDungeons.Clear();
SaveData();
}
#endregion
#region Hooks
private object CanEntityTakeDamage(BaseCombatEntity entity, HitInfo hitInfo)
{
return IsDungeonCreatedEntity(entity) ? (object)true : null;
}
private object OnEntityTakeDamage(BaseCombatEntity victim, HitInfo info)
{
if (victim == null || info == null || info.Initiator == null || !IsDungeonCreatedEntity(victim))
return null;
if (IsDungeonCreatedEntity(info.Initiator))
{
return false;
}
return null;
}
private bool IsDungeonCreatedEntity(BaseEntity entity)
{
return entity != null && _dungeonData.ActiveDungeons.Any(dungeon => dungeon.EntityIds.Contains(entity.net.ID.Value));
}
private void OnServerSave() => SaveData();
#endregion
#region Dungeon Management
[ChatCommand("removedun")]
private void RemoveDungeonCommand(BasePlayer player, string command, string[] args)
{
if (player != null && player.IsAdmin)
{
RemoveAllExistingDungeons();
player.ChatMessage("All dungeons removed.");
}
}
[ChatCommand("dun")]
private void AutoSpawnDungeonCommand(BasePlayer player, string command, string[] args)
{
if (player != null && player.IsAdmin)
{
player.ChatMessage("Attempting to spawn dungeons.");
AutoSpawnDungeon();
}
}
private void StartAutoSpawnTimer()
{
_autoSpawnTimer = timer.Every(60f, AutoSpawnDungeon);
}
private void StartDungeonCheckTimer()
{
_dungeonCheckTimer = timer.Every(60f, CheckDungeonsExistence);
}
private void AutoSpawnDungeon()
{
int activeDungeons = _dungeonData.ActiveDungeons.Count;
int dungeonsToSpawn = _configData.MaxTotalActiveDungeons - activeDungeons + 1;
for (int i = 0; i < dungeonsToSpawn; i++)
{
if (TryFindDungeonSpawnPoint(out var position, out var rotation))
{
var selectedTier = CreateDungeon(position, rotation);
if (selectedTier != null)
{
_tierNameMap.TryGetValue(selectedTier, out string tierName);
CreateDungeonMarkers(position, tierName);
NotifyPlayersOfDungeonLocation(position, tierName);
}
}
}
}
private void CreateDungeonMarkers(Vector3 position, string tierName)
{
VendingMachineMapMarker dungeonVendingMarker = GameManager.server.CreateEntity("assets/prefabs/deployable/vendingmachine/vending_mapmarker.prefab", position) as VendingMachineMapMarker;
dungeonVendingMarker.Spawn();
dungeonVendingMarker.markerShopName = $"Dungeon: {tierName}";
dungeonVendingMarker.SendNetworkUpdate();
MapMarkerGenericRadius dungeonRadiusMarker = GameManager.server.CreateEntity("assets/prefabs/tools/map/genericradiusmarker.prefab", position) as MapMarkerGenericRadius;
dungeonRadiusMarker.Spawn();
dungeonRadiusMarker.alpha = 0.75f;
dungeonRadiusMarker.radius = 0.5f;
dungeonRadiusMarker.color2 = GetMarkerColorByTier(tierName);
dungeonRadiusMarker.SendUpdate();
dungeonRadiusMarker.SendNetworkUpdate();
var marker = new DungeonMarker
{
VendingMarker = dungeonVendingMarker.net.ID.Value,
RadiusMarker = dungeonRadiusMarker.net.ID.Value,
Position = position,
Tier = tierName,
};
_dungeonData.ActiveDungeonMarkers.Add(marker);
}
private Color GetMarkerColorByTier(string tierName)
{
return tierName switch
{
"Easy" => Color.green,
"Normal" => Color.yellow,
"Medium" => Color.yellow,
"Hard" => Color.red,
"Nightmare" => Color.black,
_ => Color.white,
};
}
private void NotifyPlayersOfDungeonLocation(Vector3 position, string tierName)
{
var message = $"A {tierName} Dungeon has appeared at coordinates: ({position.x:F0}, {position.z:F0})!";
foreach (var player in BasePlayer.activePlayerList)
{
player.ShowToast(GameTip.Styles.Blue_Normal, message);
player.ChatMessage(message);
}
}
private DungeonTierConfig CreateDungeon(Vector3 position, Quaternion rotation)
{
string prefabPath = RandomlySelectDungeonPrefab();
if (string.IsNullOrEmpty(prefabPath))
{
Puts("Failed to select a valid dungeon prefab.");
return null;
}
var dungeon = GameManager.server.CreateEntity(prefabPath, position, rotation) as BasePortal;
if (dungeon == null)
{
Puts("Failed to create dungeon entity.");
return null;
}
dungeon.Spawn();
var proceduralDungeon = (dungeon as XmasDungeon)?.dungeonInstance.Get(true) ?? (dungeon as HalloweenDungeon)?.dungeonInstance.Get(true);
if (proceduralDungeon == null || proceduralDungeon.spawnedCells.Count < 1)
{
Puts("Insufficient cells in dungeon or failed to retrieve instance, destroying dungeon.");
dungeon.Kill(BaseNetworkable.DestroyMode.None);
return null;
}
var selectedTier = DetermineDungeonTier(proceduralDungeon.spawnedCells.Count);
var activeDungeon = new ActiveDungeon
{
PortalId = dungeon.net.ID.Value,
Position = position,
TierName = _tierNameMap[selectedTier],
EntityIds = new List<ulong>(),
};
NextTick(() =>
{
IntegrateEntitiesIntoDungeon(dungeon, selectedTier, activeDungeon);
});
_dungeonData.ActiveDungeons.Add(activeDungeon);
return selectedTier;
}
private object OnPortalUse(BasePlayer player, BasePortal portal)
{
if (_dungeonData.ActiveDungeons.All(d => d.PortalId != portal.net.ID.Value))
{
return null;
}
var proceduralDungeon = (portal as XmasDungeon)?.dungeonInstance.Get(true) ?? (portal as HalloweenDungeon)?.dungeonInstance.Get(true);
if (proceduralDungeon == null)
{
Puts($"No valid dungeon instance associated with the portal.");
return null;
}
foreach (var cell in proceduralDungeon.spawnedCells)
{
foreach (var group in cell.spawnGroups)
{
for (int num = group.spawnInstances.Count - 1; num >= 0; num--)
{
SpawnPointInstance spawnPointInstance = group.spawnInstances[num];
BaseEntity entity = spawnPointInstance?.gameObject?.ToBaseEntity();
if (entity != null && !IsDungeonCreatedEntity(entity) && (entity is NPCDwelling || entity is ScarecrowNPC || entity is LootContainer))
{
entity.Kill();
}
}
}
}
return null;
}
private DungeonTierConfig DetermineDungeonTier(int cellCount)
{
return cellCount switch
{
< 5 => _configData.Tiers.Easy,
< 8 => _configData.Tiers.Normal,
< 12 => _configData.Tiers.Medium,
< 16 => _configData.Tiers.Hard,
_ => _configData.Tiers.Nightmare,
};
}
private void RemoveDungeonEntities(ActiveDungeon activeDungeon)
{
foreach (var entityId in activeDungeon.EntityIds)
{
var entity = BaseNetworkable.serverEntities.Find(new NetworkableId(entityId)) as BaseEntity;
if (entity != null && !entity.IsDestroyed)
{
entity.Kill(BaseNetworkable.DestroyMode.None);
}
}
activeDungeon.EntityIds.Clear();
}
private void RemoveDungeonMarkers(ActiveDungeon dungeon)
{
foreach (var marker in _dungeonData.ActiveDungeonMarkers.Where(m => m.Position == dungeon.Position).ToList())
{
var vendingMarker = BaseNetworkable.serverEntities.Find(new NetworkableId(marker.VendingMarker)) as BaseEntity;
var radiusMarker = BaseNetworkable.serverEntities.Find(new NetworkableId(marker.RadiusMarker)) as BaseEntity;
vendingMarker?.Kill(BaseNetworkable.DestroyMode.None);
radiusMarker?.Kill(BaseNetworkable.DestroyMode.None);
_dungeonData.ActiveDungeonMarkers.Remove(marker);
}
}
private void CheckDungeonsExistence()
{
foreach (var dungeon in _dungeonData.ActiveDungeons.ToList())
{
var basePortal = BaseNetworkable.serverEntities.Find(new NetworkableId(dungeon.PortalId)) as BasePortal;
if (basePortal == null || basePortal.IsDestroyed)
{
Puts($"Dungeon at {dungeon.Position} no longer exists. Cleaning up.");
RemoveDungeonMarkers(dungeon);
RemoveDungeonEntities(dungeon);
_dungeonData.ActiveDungeons.Remove(dungeon);
}
}
}
private string RandomlySelectDungeonPrefab()
{
var availablePrefabs = new List<string>();
if (_configData.DungeonSpawn.EnableXmasDungeon)
availablePrefabs.Add("assets/prefabs/missions/portal/xmasportalentry.prefab");
if (_configData.DungeonSpawn.EnableHalloweenDungeon)
availablePrefabs.Add("assets/prefabs/missions/portal/halloweenportalentry.prefab");
return availablePrefabs.Count == 0 ? null : availablePrefabs[Random.Range(0, availablePrefabs.Count)];
}
private void IntegrateEntitiesIntoDungeon(BasePortal dungeon, DungeonTierConfig tierConfig, ActiveDungeon activeDungeon)
{
var proceduralDungeon = (dungeon as XmasDungeon)?.dungeonInstance.Get(true) ?? (dungeon as HalloweenDungeon)?.dungeonInstance.Get(true);
if (proceduralDungeon == null)
{
Puts("Failed to get procedural dungeon instance.");
return;
}
var allSpawnEntries = GatherAllSpawnEntries(tierConfig);
int totalEntities = allSpawnEntries.Count;
int cellCount = proceduralDungeon.spawnedCells.Count;
if (totalEntities == 0 || cellCount == 0)
{
Puts("No entities to spawn or no cells available.");
return;
}
float exactRatio = (float)totalEntities / cellCount;
int[] entitiesPerCell = new int[cellCount];
int assignedEntities = 0;
for (int i = 0; i < cellCount; i++)
{
entitiesPerCell[i] = (int)exactRatio;
assignedEntities += entitiesPerCell[i];
}
int remainingEntities = totalEntities - assignedEntities;
var random = new System.Random();
while (remainingEntities > 0)
{
int cellIndex = random.Next(cellCount);
entitiesPerCell[cellIndex]++;
remainingEntities--;
}
int entryIndex = 0;
for (int i = 0; i < cellCount; i++)
{
int entitiesInThisCell = entitiesPerCell[i];
if (entitiesInThisCell > 0 && entryIndex + entitiesInThisCell <= totalEntities)
{
var entriesForCell = allSpawnEntries.GetRange(entryIndex, entitiesInThisCell);
SetupSpawnGroup(proceduralDungeon.spawnedCells[i], entriesForCell, tierConfig, entitiesInThisCell, activeDungeon);
entryIndex += entitiesInThisCell;
}
}
}
private List<SpawnGroup.SpawnEntry> GatherAllSpawnEntries(DungeonTierConfig tierConfig)
{
var allSpawnEntries = new List<SpawnGroup.SpawnEntry>();
foreach (var config in tierConfig.NpcSpawnConfigs)
{
if (GameManifest.pathToGuid.TryGetValue(config.PrefabName, out var guid))
{
for (int i = 0; i < config.Total; i++)
{
allSpawnEntries.Add(
new SpawnGroup.SpawnEntry
{
prefab = new GameObjectRef { guid = guid },
weight = 1,
mobile = true,
}
);
}
}
}
for (int i = 0; i < tierConfig.AutoTurretConfig.Total; i++)
{
if (GameManifest.pathToGuid.TryGetValue("assets/prefabs/npc/autoturret/autoturret_deployed.prefab", out var turretGuid))
{
allSpawnEntries.Add(
new SpawnGroup.SpawnEntry
{
prefab = new GameObjectRef { guid = turretGuid },
weight = 1,
mobile = false,
}
);
}
}
for (int i = 0; i < tierConfig.TotalLootBoxes; i++)
{
if (GameManifest.pathToGuid.TryGetValue("assets/prefabs/deployable/woodenbox/woodbox_deployed.prefab", out var boxGuid))
{
allSpawnEntries.Add(
new SpawnGroup.SpawnEntry
{
prefab = new GameObjectRef { guid = boxGuid },
weight = 1,
mobile = false,
}
);
}
}
return allSpawnEntries.OrderBy(x => Random.value).ToList();
}
private void SetupSpawnGroup(ProceduralDungeonCell cell, List<SpawnGroup.SpawnEntry> entries, DungeonTierConfig tierConfig, int population, ActiveDungeon activeDungeon)
{
var spawnGroup = cell.gameObject.AddComponent<SpawnGroup>();
spawnGroup.prefabs = entries;
spawnGroup.maxPopulation = population;
spawnGroup.numToSpawnPerTickMin = 0;
spawnGroup.numToSpawnPerTickMax = 0;
spawnGroup.fillOnSpawn = true;
spawnGroup.wantsInitialSpawn = true;
spawnGroup.SpawnInitial();
foreach (var instance in spawnGroup.spawnInstances)
{
var entity = instance.GetComponent<BaseEntity>();
if (entity != null && !activeDungeon.EntityIds.Contains(entity.net.ID.Value))
{
activeDungeon.EntityIds.Add(entity.net.ID.Value);
if (entity is AutoTurret autoTurret)
{
ConfigureAutoTurret(autoTurret, tierConfig.AutoTurretConfig);
autoTurret.gameObject.AddComponent<TurretBehaviour>();
}
if (entity is StorageContainer box)
{
ConfigureLootBox(box);
}
}
}
}
private void ConfigureLootBox(StorageContainer box)
{
box.skinID = _configData.LootBoxConfig.SmallWoodBoxSkinID;
AddLockToBox(box);
FillLootBox(box.inventory, _configData.LootBoxConfig.LootItems);
}
private void AddLockToBox(StorageContainer box)
{
var codeLock = GameManager.server.CreateEntity("assets/prefabs/locks/keypad/lock.code.prefab") as CodeLock;
if (codeLock != null)
{
codeLock.SetParent(box, box.GetSlotAnchorName(BaseEntity.Slot.Lock));
codeLock.Spawn();
codeLock.code = Random.Range(1000, 9999).ToString();
codeLock.hasCode = true;
codeLock.guestCode = string.Empty;
codeLock.hasGuestCode = false;
codeLock.guestPlayers.Clear();
codeLock.whitelistPlayers.Clear();
codeLock.SetFlag(BaseEntity.Flags.Locked, true);
}
}
private void FillLootBox(ItemContainer container, List<ItemConfig> lootItems)
{
var shuffledItems = lootItems.OrderBy(_ => Random.value).ToList();
int maxDifferentItems = _configData.LootBoxConfig.MaxDifferentItemsPerBox;
int differentItemsCount = 0;
foreach (var itemInfo in shuffledItems)
{
if (differentItemsCount >= maxDifferentItems)
break;
if (Random.Range(0f, 100f) <= itemInfo.InclusionChancePercentage)
{
var itemDefinition = ItemManager.FindItemDefinition(itemInfo.ShortName);
if (itemDefinition != null)
{
int amount = Random.Range(itemInfo.MinimumAmount, itemInfo.MaximumAmount + 1);
if (container.itemList.Count < container.capacity)
{
var item = ItemManager.Create(itemDefinition, amount);
item.MoveToContainer(container);
differentItemsCount++;
}
}
}
}
}
private void ConfigureAutoTurret(AutoTurret autoTurret, TurretConfig turretConfig)
{
autoTurret.health = turretConfig.Health;
autoTurret.SetMaxHealth(autoTurret.health);
var weapon = ItemManager.CreateByName(turretConfig.WeaponShortName);
if (weapon != null)
{
weapon.MoveToContainer(autoTurret.inventory, 0);
var laserSight = ItemManager.CreateByName("weapon.mod.lasersight");
laserSight?.MoveToContainer(weapon.contents);
autoTurret.UpdateAttachedWeapon();
}
}
#endregion
#region Utility and Cleanup
private bool TryFindDungeonSpawnPoint(out Vector3 suitablePosition, out Quaternion suitableRotation)
{
float mapSize = World.Size;
for (int attempt = 0; attempt < 100; attempt++)
{
Vector3 candidatePosition = new Vector3(Random.Range(-mapSize / 2, mapSize / 2), 0, Random.Range(-mapSize / 2, mapSize / 2));
if (IsValidSpawnPosition(candidatePosition, out suitablePosition, out suitableRotation))
{
bool tooClose = false;
foreach (var dungeon in _dungeonData.ActiveDungeons)
{
if (Vector3.Distance(suitablePosition, dungeon.Position) < minDistanceBetweenDungeons)
{
tooClose = true;
break;
}
}
if (!tooClose)
return true;
}
}
suitablePosition = Vector3.zero;
suitableRotation = Quaternion.identity;
return false;
}
private bool IsValidSpawnPosition(Vector3 position, out Vector3 suitablePosition, out Quaternion suitableRotation)
{
suitablePosition = Vector3.zero;
suitableRotation = Quaternion.identity;
if (TerrainUtilities.InsideRock(position, _configData.AutoSpawn.RocksAvoidanceRadius))
return false;
if (TerrainUtilities.InRadTown(position) || TerrainUtilities.HasEntityNearby(position, _configData.AutoSpawn.NearbyEntitiesAvoidanceRadius, Layers.Mask.Construction))
return false;
if (TerrainUtilities.InWater(position) || TerrainUtilities.OnRoadOrRail(position))
return false;
if (TerrainUtilities.InNoBuildZone(position, _configData.AutoSpawn.DistanceFromNoBuildZones))
return false;
if (TerrainUtilities.GetTerrainInfo(position, out var hitInfo))
{
suitablePosition = hitInfo.point;
suitableRotation = Quaternion.FromToRotation(Vector3.up, hitInfo.normal);
return true;
}
return false;
}
private void Unload()
{
SaveData();
RemoveAllExistingDungeons();
_autoSpawnTimer?.Destroy();
_dungeonCheckTimer?.Destroy();
}
#endregion
#region Terrain Utilities
public static class TerrainUtilities
{
private const int WorldLayer = Layers.Mask.World;
private const int TerrainLayer = Layers.Mask.Terrain;
private const int PreventBuildingLayer = Layers.Mask.Prevent_Building;
private const int ConstructionLayer = Layers.Mask.Construction;
public static Vector3 GetRandomPosition(float minX, float maxX, float minZ, float maxZ)
{
float randomX = Random.Range(minX, maxX);
float randomZ = Random.Range(minZ, maxZ);
float y = TerrainMeta.HeightMap.GetHeight(new Vector3(randomX, 0, randomZ));
return new Vector3(randomX, y, randomZ);
}
public static bool InsideRock(Vector3 position, float radius)
{
List<Collider> colliders = Pool.GetList<Collider>();
Vis.Colliders(position, radius, colliders, WorldLayer, QueryTriggerInteraction.Ignore);
bool isInsideRock = colliders.Any(collider =>
collider.name.Contains("rock", StringComparison.OrdinalIgnoreCase)
|| collider.name.Contains("cliff", StringComparison.OrdinalIgnoreCase)
|| collider.name.Contains("formation", StringComparison.OrdinalIgnoreCase)
);
Pool.FreeList(ref colliders);
return isInsideRock;
}
public static bool InRadTown(Vector3 position)
{
return TerrainMeta.Path.Monuments.Any(monument => monument.IsInBounds(position) && monument.shouldDisplayOnMap)
|| (TerrainMeta.TopologyMap.GetTopology(position) & (int)TerrainTopology.Enum.Monument) != 0;
}
public static bool HasEntityNearby(Vector3 position, float radius, int mask, string prefabName = null)
{
List<Collider> hitColliders = Pool.GetList<Collider>();
GamePhysics.OverlapSphere(position, radius, hitColliders, mask, QueryTriggerInteraction.Ignore);
bool result = hitColliders.Any(collider =>
{
BaseEntity entity = collider.gameObject.ToBaseEntity();
return entity != null && (prefabName == null || entity.PrefabName == prefabName);
});
Pool.FreeList(ref hitColliders);
return result;
}
public static bool InWater(Vector3 position)
{
return WaterLevel.Test(position, false, false);
}
public static bool OnRoadOrRail(Vector3 position)
{
int topology = TerrainMeta.TopologyMap.GetTopology(position);
return (topology & (int)(TerrainTopology.Enum.Road | TerrainTopology.Enum.Roadside | TerrainTopology.Enum.Rail | TerrainTopology.Enum.Railside)) != 0;
}
public static bool GetTerrainInfo(Vector3 startPosition, out RaycastHit hitInfo, float range = 1f, LayerMask mask = default)
{
mask = mask == default ? TerrainLayer : mask;
return Physics.Linecast(startPosition + Vector3.up * range, startPosition - Vector3.up * range, out hitInfo, mask);
}
public static bool InNoBuildZone(Vector3 position, float radius)
{
return Physics.CheckSphere(position, radius, PreventBuildingLayer, QueryTriggerInteraction.Ignore);
}
}
#endregion
#region Configuration
private class ConfigData
{
[JsonProperty(PropertyName = "Dungeon Spawn Settings")]
public DungeonSpawnSettings DungeonSpawn { get; set; } = new DungeonSpawnSettings();
[JsonProperty(PropertyName = "Auto Spawn Settings")]
public AutoSpawnSettings AutoSpawn { get; set; } = new AutoSpawnSettings();
[JsonProperty(PropertyName = "Loot Box Config")]
public LootBoxConfig LootBoxConfig { get; set; } = new LootBoxConfig();
[JsonProperty(PropertyName = "Max Total Active Dungeons")]
public int MaxTotalActiveDungeons { get; set; } = 3;
[JsonProperty(PropertyName = "Tiers")]
public DungeonTiers Tiers { get; set; } = new DungeonTiers();
[JsonProperty(PropertyName = "Version")]
public VersionNumber Version { get; set; }
}
private class DungeonTiers
{
[JsonProperty(PropertyName = "Easy")]
public DungeonTierConfig Easy { get; set; } =
new DungeonTierConfig
{
TotalLootBoxes = 1,
NpcSpawnConfigs = new List<NpcSpawnConfig>
{
new NpcSpawnConfig { PrefabName = "assets/rust.ai/agents/npcplayer/humannpc/scientist/scientistnpc_full_pistol.prefab", Total = 1 },
},
AutoTurretConfig = new TurretConfig
{
Total = 1,
Health = 300,
WeaponShortName = "pistol.revolver",
},
};
[JsonProperty(PropertyName = "Normal")]
public DungeonTierConfig Normal { get; set; } =
new DungeonTierConfig
{
TotalLootBoxes = 2,
NpcSpawnConfigs = new List<NpcSpawnConfig>
{
new NpcSpawnConfig { PrefabName = "assets/rust.ai/agents/npcplayer/humannpc/scientist/scientistnpc_full_shotgun.prefab", Total = 3 },
},
AutoTurretConfig = new TurretConfig
{
Total = 2,
Health = 600,
WeaponShortName = "smg.2",
},
};
[JsonProperty(PropertyName = "Medium")]
public DungeonTierConfig Medium { get; set; } =
new DungeonTierConfig
{
TotalLootBoxes = 4,
NpcSpawnConfigs = new List<NpcSpawnConfig>
{
new NpcSpawnConfig { PrefabName = "assets/rust.ai/agents/npcplayer/humannpc/banditguard/npc_bandit_guard.prefab", Total = 4 },
},
AutoTurretConfig = new TurretConfig
{
Total = 3,
Health = 1000,
WeaponShortName = "smg.mp5",
},
};
[JsonProperty(PropertyName = "Hard")]
public DungeonTierConfig Hard { get; set; } =
new DungeonTierConfig
{
TotalLootBoxes = 5,
NpcSpawnConfigs = new List<NpcSpawnConfig>
{
new NpcSpawnConfig { PrefabName = "assets/rust.ai/agents/npcplayer/humannpc/scientist/scientistnpc_cargo_turret_lr300.prefab", Total = 4 },
new NpcSpawnConfig { PrefabName = "assets/rust.ai/agents/npcplayer/humannpc/scientist/scientistnpc_cargo_turret_lr300.prefab", Total = 3 },
},
AutoTurretConfig = new TurretConfig
{
Total = 4,
Health = 1500,
WeaponShortName = "rifle.ak",
},
};
[JsonProperty(PropertyName = "Nightmare")]
public DungeonTierConfig Nightmare { get; set; } =
new DungeonTierConfig
{
TotalLootBoxes = 6,
NpcSpawnConfigs = new List<NpcSpawnConfig>
{
new NpcSpawnConfig { PrefabName = "assets/rust.ai/agents/npcplayer/humannpc/scientist/scientistnpc_ch47_gunner.prefab", Total = 5 },
new NpcSpawnConfig { PrefabName = "assets/rust.ai/agents/npcplayer/humannpc/scientist/scientistnpc_cargo_turret_lr300.prefab", Total = 5 },
},
AutoTurretConfig = new TurretConfig
{
Total = 6,
Health = 2000,
WeaponShortName = "rifle.ak",
},
};
}
private class DungeonTierConfig
{
[JsonProperty(PropertyName = "Maximum Loot Boxes")]
public int TotalLootBoxes { get; set; }
[JsonProperty(PropertyName = "NPC Spawn Configs", ObjectCreationHandling = ObjectCreationHandling.Replace)]
public List<NpcSpawnConfig> NpcSpawnConfigs { get; set; }
[JsonProperty(PropertyName = "Auto Turret Config")]
public TurretConfig AutoTurretConfig { get; set; } = new TurretConfig();
}
public class LootBoxConfig
{
[JsonProperty(PropertyName = "Small Wood Box Skin ID")]
public ulong SmallWoodBoxSkinID { get; set; } = 2998755525;
[JsonProperty(PropertyName = "Max Different Items Per Box")]
public int MaxDifferentItemsPerBox { get; set; } = 6;
[JsonProperty(PropertyName = "Loot Items", ObjectCreationHandling = ObjectCreationHandling.Replace)]
public List<ItemConfig> LootItems { get; set; } =
new List<ItemConfig>
{
new ItemConfig
{
ShortName = "ammo.rifle",
InclusionChancePercentage = 15,
MinimumAmount = 100,
MaximumAmount = 300,
},
new ItemConfig
{
ShortName = "sulfur.ore",
InclusionChancePercentage = 15,
MinimumAmount = 100,
MaximumAmount = 300,
},
new ItemConfig
{
ShortName = "scrap",
InclusionChancePercentage = 70,
MinimumAmount = 500,
MaximumAmount = 2000,
},
new ItemConfig
{
ShortName = "rifle.ak",
InclusionChancePercentage = 5,
MinimumAmount = 1,
MaximumAmount = 1,
},
new ItemConfig
{
ShortName = "rocket.launcher.dragon",
InclusionChancePercentage = 5,
MinimumAmount = 1,
MaximumAmount = 1,
},
new ItemConfig
{
ShortName = "grenade.f1",
InclusionChancePercentage = 10,
MinimumAmount = 1,
MaximumAmount = 5,
},
new ItemConfig
{
ShortName = "crude.oil",
InclusionChancePercentage = 40,
MinimumAmount = 10,
MaximumAmount = 100,
},
new ItemConfig
{
ShortName = "diesel_barrel",
InclusionChancePercentage = 30,
MinimumAmount = 1,
MaximumAmount = 5,
},
new ItemConfig
{
ShortName = "cctv.camera",
InclusionChancePercentage = 20,
MinimumAmount = 5,
MaximumAmount = 20,
},
new ItemConfig
{
ShortName = "gingerbreadsuit",
InclusionChancePercentage = 5,
MinimumAmount = 1,
MaximumAmount = 1,
},
new ItemConfig
{
ShortName = "gears",
InclusionChancePercentage = 20,
MinimumAmount = 1,
MaximumAmount = 20,
},
new ItemConfig
{
ShortName = "metal.refined",
InclusionChancePercentage = 20,
MinimumAmount = 10,
MaximumAmount = 80,
},
new ItemConfig
{
ShortName = "electric.furnace",
InclusionChancePercentage = 20,
MinimumAmount = 1,
MaximumAmount = 1,
},
new ItemConfig
{
ShortName = "fuse",
InclusionChancePercentage = 20,
MinimumAmount = 1,
MaximumAmount = 1,
},
new ItemConfig
{
ShortName = "xmas.door.garland",
InclusionChancePercentage = 10,
MinimumAmount = 1,
MaximumAmount = 1,
},
new ItemConfig
{
ShortName = "gunpowder",
InclusionChancePercentage = 50,
MinimumAmount = 50,
MaximumAmount = 150,
},
new ItemConfig
{
ShortName = "handcuffs",
InclusionChancePercentage = 10,
MinimumAmount = 1,
MaximumAmount = 1,
},
new ItemConfig
{
ShortName = "hazmatsuit",
InclusionChancePercentage = 10,
MinimumAmount = 1,
MaximumAmount = 1,
},
new ItemConfig
{
ShortName = "ammo.rocket.hv",
InclusionChancePercentage = 10,
MinimumAmount = 1,
MaximumAmount = 20,
},
new ItemConfig
{
ShortName = "ammo.rifle.hv",
InclusionChancePercentage = 10,
MinimumAmount = 100,
MaximumAmount = 300,
},
new ItemConfig
{
ShortName = "metal.facemask",
InclusionChancePercentage = 25,
MinimumAmount = 1,
MaximumAmount = 1,
},
new ItemConfig
{
ShortName = "ammo.rocket.fire",
InclusionChancePercentage = 10,
MinimumAmount = 5,
MaximumAmount = 10,
},
new ItemConfig
{
ShortName = "jackhammer",
InclusionChancePercentage = 30,
MinimumAmount = 1,
MaximumAmount = 1,
},
new ItemConfig
{
ShortName = "rifle.l96",
InclusionChancePercentage = 5,
MinimumAmount = 1,
MaximumAmount = 1,
},
new ItemConfig
{
ShortName = "furnace.large",
InclusionChancePercentage = 50,
MinimumAmount = 1,
MaximumAmount = 1,
},
new ItemConfig
{
ShortName = "largemedkit",
InclusionChancePercentage = 50,
MinimumAmount = 1,
MaximumAmount = 10,
},
new ItemConfig
{
ShortName = "xmas.present.large",
InclusionChancePercentage = 40,
MinimumAmount = 1,
MaximumAmount = 1,
},
new ItemConfig
{
ShortName = "locker",
InclusionChancePercentage = 40,
MinimumAmount = 1,
MaximumAmount = 1,
},
new ItemConfig
{
ShortName = "lowgradefuel",
InclusionChancePercentage = 70,
MinimumAmount = 50,
MaximumAmount = 200,
},
new ItemConfig
{
ShortName = "rifle.lr300",
InclusionChancePercentage = 5,
MinimumAmount = 1,
MaximumAmount = 1,
},
new ItemConfig
{
ShortName = "rifle.m39",
InclusionChancePercentage = 5,
MinimumAmount = 1,
MaximumAmount = 1,
},
new ItemConfig
{
ShortName = "pistol.m92",
InclusionChancePercentage = 10,
MinimumAmount = 1,
MaximumAmount = 1,
},
new ItemConfig
{
ShortName = "lmg.m249",
InclusionChancePercentage = 2,
MinimumAmount = 1,
MaximumAmount = 1,
},
new ItemConfig
{
ShortName = "syringe.medical",
InclusionChancePercentage = 70,
MinimumAmount = 10,
MaximumAmount = 30,
},
new ItemConfig
{
ShortName = "metal.ore",
InclusionChancePercentage = 80,
MinimumAmount = 100,
MaximumAmount = 300,
},
new ItemConfig
{
ShortName = "metalpipe",
InclusionChancePercentage = 60,
MinimumAmount = 1,
MaximumAmount = 5,
},
new ItemConfig
{
ShortName = "metalspring",
InclusionChancePercentage = 60,
MinimumAmount = 1,
MaximumAmount = 5,
},
new ItemConfig
{
ShortName = "minigun",
InclusionChancePercentage = 2,
MinimumAmount = 1,
MaximumAmount = 1,
},
new ItemConfig
{
ShortName = "grenade.molotov",
InclusionChancePercentage = 10,
MinimumAmount = 1,
MaximumAmount = 5,
},
new ItemConfig
{
ShortName = "smg.mp5",
InclusionChancePercentage = 4,
MinimumAmount = 1,
MaximumAmount = 1,
},
new ItemConfig
{
ShortName = "multiplegrenadelauncher",
InclusionChancePercentage = 15,
MinimumAmount = 1,
MaximumAmount = 1,
},
new ItemConfig
{
ShortName = "mummymask",
InclusionChancePercentage = 20,
MinimumAmount = 1,
MaximumAmount = 1,
},
new ItemConfig
{
ShortName = "mushroom",
InclusionChancePercentage = 80,
MinimumAmount = 20,
MaximumAmount = 40,
},
new ItemConfig
{
ShortName = "weapon.mod.muzzleboost",
InclusionChancePercentage = 35,
MinimumAmount = 1,
MaximumAmount = 1,
},
new ItemConfig
{
ShortName = "weapon.mod.muzzlebrake",
InclusionChancePercentage = 35,
MinimumAmount = 1,
MaximumAmount = 1,
},
new ItemConfig
{
ShortName = "nightvisiongoggles",
InclusionChancePercentage = 20,
MinimumAmount = 1,
MaximumAmount = 1,
},
new ItemConfig
{
ShortName = "newyeargong",
InclusionChancePercentage = 25,
MinimumAmount = 1,
MaximumAmount = 1,
},
new ItemConfig
{
ShortName = "hazmatsuit.nomadsuit",
InclusionChancePercentage = 20,
MinimumAmount = 1,
MaximumAmount = 1,
},
new ItemConfig
{
ShortName = "firework.boomer.pattern",
InclusionChancePercentage = 40,
MinimumAmount = 1,
MaximumAmount = 5,
},
new ItemConfig
{
ShortName = "jar.pickle",
InclusionChancePercentage = 60,
MinimumAmount = 1,
MaximumAmount = 1,
},
new ItemConfig
{
ShortName = "ammo.pistol",
InclusionChancePercentage = 70,
MinimumAmount = 100,
MaximumAmount = 500,
},
new ItemConfig
{
ShortName = "pookie.bear",
InclusionChancePercentage = 60,
MinimumAmount = 1,
MaximumAmount = 1,
},
new ItemConfig
{
ShortName = "potato",
InclusionChancePercentage = 70,
MinimumAmount = 100,
MaximumAmount = 100,
},
new ItemConfig
{
ShortName = "pistol.revolver",
InclusionChancePercentage = 50,
MinimumAmount = 1,
MaximumAmount = 1,
},
new ItemConfig
{
ShortName = "riflebody",
InclusionChancePercentage = 60,
MinimumAmount = 5,
MaximumAmount = 10,
},
new ItemConfig
{
ShortName = "roadsign.gloves",
InclusionChancePercentage = 40,
MinimumAmount = 1,
MaximumAmount = 1,
},
new ItemConfig
{
ShortName = "roadsign.kilt",
InclusionChancePercentage = 40,
MinimumAmount = 1,
MaximumAmount = 1,
},
new ItemConfig
{
ShortName = "ammo.rocket.basic",
InclusionChancePercentage = 30,
MinimumAmount = 1,
MaximumAmount = 5,
},
new ItemConfig
{
ShortName = "rocket.launcher",
InclusionChancePercentage = 5,
MinimumAmount = 1,
MaximumAmount = 1,
},
new ItemConfig
{
ShortName = "rope",
InclusionChancePercentage = 60,
MinimumAmount = 5,
MaximumAmount = 10,
},
new ItemConfig
{
ShortName = "icepick.salvaged",
InclusionChancePercentage = 30,
MinimumAmount = 1,
MaximumAmount = 1,
},
new ItemConfig
{
ShortName = "explosive.satchel",
InclusionChancePercentage = 25,
MinimumAmount = 1,
MaximumAmount = 5,
},
new ItemConfig
{
ShortName = "electric.seismicsensor",
InclusionChancePercentage = 20,
MinimumAmount = 1,
MaximumAmount = 1,
},
new ItemConfig
{
ShortName = "semibody",
InclusionChancePercentage = 60,
MinimumAmount = 5,
MaximumAmount = 10,
},
new ItemConfig
{
ShortName = "pistol.semiauto",
InclusionChancePercentage = 10,
MinimumAmount = 1,
MaximumAmount = 1,
},
new ItemConfig
{
ShortName = "rifle.semiauto",
InclusionChancePercentage = 5,
MinimumAmount = 1,
MaximumAmount = 1,
},
new ItemConfig
{
ShortName = "sheetmetal",
InclusionChancePercentage = 70,
MinimumAmount = 1,
MaximumAmount = 9,
},
new ItemConfig
{
ShortName = "sewingkit",
InclusionChancePercentage = 65,
MinimumAmount = 1,
MaximumAmount = 10,
},
new ItemConfig
{
ShortName = "weapon.mod.silencer",
InclusionChancePercentage = 10,
MinimumAmount = 1,
MaximumAmount = 1,
},
new ItemConfig
{
ShortName = "weapon.mod.simplesight",
InclusionChancePercentage = 50,
MinimumAmount = 1,
MaximumAmount = 1,
},
new ItemConfig
{
ShortName = "knife.skinning",
InclusionChancePercentage = 50,
MinimumAmount = 1,
MaximumAmount = 1,
},
new ItemConfig
{
ShortName = "rifle.sks",
InclusionChancePercentage = 7,
MinimumAmount = 1,
MaximumAmount = 1,
},
new ItemConfig
{
ShortName = "xmas.present.small",
InclusionChancePercentage = 40,
MinimumAmount = 1,
MaximumAmount = 1,
},
new ItemConfig
{
ShortName = "smgbody",
InclusionChancePercentage = 60,
MinimumAmount = 5,
MaximumAmount = 9,
},
new ItemConfig
{
ShortName = "sofa",
InclusionChancePercentage = 30,
MinimumAmount = 1,
MaximumAmount = 1,
},
new ItemConfig
{
ShortName = "hazmatsuit.spacesuit",
InclusionChancePercentage = 10,
MinimumAmount = 1,
MaximumAmount = 1,
},
new ItemConfig
{
ShortName = "spookyspeaker",
InclusionChancePercentage = 25,
MinimumAmount = 1,
MaximumAmount = 1,
},
new ItemConfig
{
ShortName = "stones",
InclusionChancePercentage = 80,
MinimumAmount = 100,
MaximumAmount = 600,
},
new ItemConfig
{
ShortName = "sulfur",
InclusionChancePercentage = 80,
MinimumAmount = 100,
MaximumAmount = 600,
},
new ItemConfig
{
ShortName = "tactical.gloves",
InclusionChancePercentage = 35,
MinimumAmount = 1,
MaximumAmount = 1,
},
new ItemConfig
{
ShortName = "tarp",
InclusionChancePercentage = 60,
MinimumAmount = 1,
MaximumAmount = 5,
},
new ItemConfig
{
ShortName = "vampire.stake",
InclusionChancePercentage = 20,
MinimumAmount = 1,
MaximumAmount = 1,
},
new ItemConfig
{
ShortName = "vending.machine",
InclusionChancePercentage = 15,
MinimumAmount = 1,
MaximumAmount = 1,
},
new ItemConfig
{
ShortName = "bottle.vodka",
InclusionChancePercentage = 9,
MinimumAmount = 1,
MaximumAmount = 1,
},
new ItemConfig
{
ShortName = "gun.water",
InclusionChancePercentage = 9,
MinimumAmount = 1,
MaximumAmount = 1,
},
new ItemConfig
{
ShortName = "piano",
InclusionChancePercentage = 9,
MinimumAmount = 1,
MaximumAmount = 1,
},
new ItemConfig
{
ShortName = "firework.volcano",
InclusionChancePercentage = 30,
MinimumAmount = 1,
MaximumAmount = 5,
},
new ItemConfig
{
ShortName = "hat.wolf",
InclusionChancePercentage = 50,
MinimumAmount = 1,
MaximumAmount = 1,
},
new ItemConfig
{
ShortName = "worm",
InclusionChancePercentage = 70,
MinimumAmount = 1,
MaximumAmount = 1,
},
};
}
private class AutoSpawnSettings
{
[JsonProperty(PropertyName = "Enable Auto Spawn")]
public bool EnableAutoSpawn { get; set; } = true;
[JsonProperty(PropertyName = "Nearby Entities Avoidance Radius")]
public float NearbyEntitiesAvoidanceRadius { get; set; } = 10f;
[JsonProperty(PropertyName = "Rocks Avoidance Radius")]
public float RocksAvoidanceRadius { get; set; } = 10f;
[JsonProperty(PropertyName = "Distance From No Build Zones")]
public float DistanceFromNoBuildZones { get; set; } = 10f;
}
private class NpcSpawnConfig
{
[JsonProperty(PropertyName = "Prefab")]
public string PrefabName { get; set; }
[JsonProperty(PropertyName = "Maximum")]
public int Total { get; set; }
}
private class TurretConfig
{
[JsonProperty(PropertyName = "Maximum")]
public int Total { get; set; } = 3;
[JsonProperty(PropertyName = "Health")]
public float Health { get; set; } = 1000f;
[JsonProperty(PropertyName = "Weapon Short Name")]
public string WeaponShortName { get; set; } = "rifle.ak";
}
private class DungeonSpawnSettings
{
[JsonProperty(PropertyName = "Enable Xmas Dungeon")]
public bool EnableXmasDungeon { get; set; } = true;
[JsonProperty(PropertyName = "Enable Halloween Dungeon")]
public bool EnableHalloweenDungeon { get; set; } = false;
}
public class ItemConfig
{
[JsonProperty(PropertyName = "ShortName")]
public string ShortName { get; set; }
[JsonProperty(PropertyName = "Inclusion Chance Percentage")]
public float InclusionChancePercentage { get; set; }
[JsonProperty(PropertyName = "Minimum Amount")]
public int MinimumAmount { get; set; }
[JsonProperty(PropertyName = "Maximum Amount")]
public int MaximumAmount { get; set; }
}
protected override void LoadConfig()
{
base.LoadConfig();
try
{
_configData = Config.ReadObject<ConfigData>();
if (_configData == null)
{
LoadDefaultConfig();
}
}
catch (Exception ex)
{
PrintError($"The configuration file is corrupted: {ex}");
LoadDefaultConfig();
}
SaveConfig();
}
protected override void LoadDefaultConfig()
{
PrintWarning("Creating a new configuration file");
_configData = new ConfigData();
_configData.Version = Version;
}
protected override void SaveConfig()
{
Config.WriteObject(_configData, true);
}
#endregion
#region Data Classes
private class DungeonData
{
public List<ActiveDungeon> ActiveDungeons { get; set; } = new();
public List<DungeonMarker> ActiveDungeonMarkers { get; set; } = new();
}
private class ActiveDungeon
{
public ulong PortalId { get; set; }
public Vector3 Position { get; set; }
public string TierName { get; set; }
public List<ulong> EntityIds { get; set; } = new();
}
private class DungeonMarker
{
public ulong VendingMarker { get; set; }
public ulong RadiusMarker { get; set; }
public Vector3 Position { get; set; }
public string Tier { get; set; }
}
#endregion
#region Turret Behaviour Class
public class TurretBehaviour : MonoBehaviour
{
private AutoTurret _turret;
private const float SearchDistance = 15f;
private const int MaxReserveAmmo = 1000;
private void Awake()
{
InitializeTurret();
}
private void InitializeTurret()
{
_turret = GetComponent<AutoTurret>();
var triggerCollider = _turret.targetTrigger.GetComponent<SphereCollider>();
triggerCollider.enabled = false;
_turret.SetPeacekeepermode(false);
_turret.InitiateStartup();
_turret.SetIsOnline(true);
_turret.CancelInvoke(_turret.ServerTick);
_turret.SetTarget(null);
_turret.InvokeRepeating(PerformTurretCycle, 1f, 0.01f);
_turret.InvokeRepeating(ScanForTargets, 2f, 1f);
_turret.isLootable = false;
_turret.dropFloats = false;
_turret.dropsLoot = false;
_turret.SendNetworkUpdateImmediate();
InvokeRepeating(nameof(RefillAmmo), 2f, 30f);
}
private void RefillAmmo()
{
if (_turret.AttachedWeapon is not BaseProjectile baseProjectile || baseProjectile.primaryMagazine?.ammoType == null)
return;
var ammoType = baseProjectile.primaryMagazine.ammoType;
int currentAmmoCount = CalculateCurrentAmmoCount(ammoType);
if (currentAmmoCount < MaxReserveAmmo)
{
int ammoNeeded = MaxReserveAmmo - currentAmmoCount;
Item ammoItem = ItemManager.Create(ammoType, ammoNeeded);
ammoItem?.MoveToContainer(_turret.inventory);
_turret.UpdateTotalAmmo();
_turret.EnsureReloaded();
_turret.SendNetworkUpdateImmediate();
}
}
private int CalculateCurrentAmmoCount(ItemDefinition ammoType)
{
return _turret.inventory.itemList.Where(item => item.info == ammoType).Sum(item => item.amount);
}
private void ScanForTargets()
{
var entityContents = _turret.targetTrigger.entityContents ??= new HashSet<BaseEntity>();
entityContents.Clear();
int foundTargets = BaseEntity.Query.Server.GetPlayersInSphereFast(transform.position, SearchDistance, AIBrainSenses.playerQueryResults, IsTargetValid);
if (foundTargets == 0)
return;
_turret.authDirty = true;
for (int i = 0; i < foundTargets; i++)
{
var player = AIBrainSenses.playerQueryResults[i];
if (Interface.CallHook("OnEntityEnter", _turret.targetTrigger, player) != null || player.IsSleeping() || (player.InSafeZone() && !player.IsHostile()))
continue;
entityContents.Add(player);
}
}
private bool IsTargetValid(BasePlayer player) => player != null && !player.IsNpc;
private void PerformTurretCycle()
{
if (_turret.isClient || _turret.IsDestroyed)
return;
float deltaTime = (float)_turret.timeSinceLastServerTick;
_turret.timeSinceLastServerTick = 0f;
if (_turret.IsOnline() && !_turret.IsBeingControlled)
{
if (!_turret.HasTarget())
{
_turret.IdleTick(deltaTime);
}
else
{
ExecuteTargetEngagement();
}
}
_turret.UpdateFacingToTarget(deltaTime);
UpdateAmmoStatus();
}
private void ExecuteTargetEngagement()
{
if (Time.realtimeSinceStartup >= _turret.nextVisCheck)
{
_turret.nextVisCheck = Time.realtimeSinceStartup + UnityEngine.Random.Range(0.2f, 0.3f);
_turret.targetVisible = _turret.ObjectVisible(_turret.target);
if (_turret.targetVisible)
_turret.lastTargetSeenTime = Time.realtimeSinceStartup;
}
_turret.EnsureReloaded();
if (ShouldFireAtTarget())
{
var weapon = _turret.GetAttachedWeapon();
FireWeapon(weapon);
}
ValidateTargetEngagement();
}
private bool ShouldFireAtTarget()
{
return Time.time >= _turret.nextShotTime
&& _turret.targetVisible
&& Mathf.Abs(_turret.AngleToTarget(_turret.target, _turret.currentAmmoGravity != 0f)) < _turret.GetMaxAngleForEngagement();
}
private void FireWeapon(BaseProjectile weapon)
{
if (weapon == null)
{
_turret.nextShotTime = Time.time + 1f;
return;
}
if (weapon.primaryMagazine.contents > 0)
{
_turret.FireAttachedGun(_turret.AimOffset(_turret.target), _turret.aimCone, null, _turret.PeacekeeperMode() ? _turret.target : null);
float delay = weapon.isSemiAuto ? weapon.repeatDelay * 1.5f : weapon.repeatDelay;
delay = weapon.ScaleRepeatDelay(delay);
_turret.nextShotTime = Time.time + delay;
}
else
{
_turret.nextShotTime = Time.time + 5f;
}
}
private void ValidateTargetEngagement()
{
var targetPlayer = _turret.target as BasePlayer;
if (_turret.target != null && (!IsValidSteamPlayer(targetPlayer) || _turret.target.IsDead() || TimeExceedsLastSeen() || DistanceExceedsSightRange() || ShouldTargetBeReset()))
{
_turret.SetTarget(null);
}
}
private static bool IsValidSteamPlayer(BasePlayer targetPlayer) => targetPlayer != null && targetPlayer.userID.IsSteamId();
private bool TimeExceedsLastSeen() => Time.realtimeSinceStartup - _turret.lastTargetSeenTime > 3f;
private bool DistanceExceedsSightRange() => Vector3.Distance(_turret.transform.position, _turret.target.transform.position) > _turret.sightRange;
private bool ShouldTargetBeReset() => _turret.PeacekeeperMode() && !_turret.IsEntityHostile(_turret.target);
private void UpdateAmmoStatus()
{
if (_turret.totalAmmoDirty && Time.time > _turret.nextAmmoCheckTime)
{
_turret.UpdateTotalAmmo();
_turret.totalAmmoDirty = false;
_turret.nextAmmoCheckTime = Time.time + 0.5f;
}
}
private void OnDestroy()
{
CancelInvoke(nameof(RefillAmmo));
}
}
#endregion
}
}