turns-00052.parquet:31308
a54364d746152a1fc9dabbb0
turn 8/15gpt-4o-2024-08-06EnglishBrazil5074 words
degenerate_repetitionAbsentFinal dense release
USER
não faz sentido, tente achar o problema, estou mandando tudo novamente:
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(Msg(player.UserIDString, "AllDungeonsRemoved"));
}
}
[ChatCommand("dun")]
private void AutoSpawnDungeonCommand(BasePlayer player, string command, string[] args)
{
if (player != null && player.IsAdmin)
{
player.ChatMessage(Msg(player.UserIDString, "AttemptSpawnDungeons"));
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)
{
string gridPosition = PhoneController.PositionToGridCoord(position);
foreach (var player in BasePlayer.activePlayerList)
{
string message = Msg(player.UserIDString, "DungeonAppeared", tierName, gridPosition);
Puts($"Formatted Message: {message}");
//player.ChatMessage(message);
//player.ShowToast(GameTip.Styles.Blue_Normal, 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 Localization
protected override void LoadDefaultMessages()
{
lang.RegisterMessages(
new Dictionary<string, string>
{
["AllDungeonsRemoved"] = "All dungeons removed.",
["AttemptSpawnDungeons"] = "Attempting to spawn dungeons.",
["DungeonAppeared"] = "A {0} Dungeon has appeared at {1}!",
["NoPermission"] = "You don't have permission to use this command.",
["DungeonSpawnFailed"] = "Failed to select a valid dungeon prefab.",
["DungeonEntityCreationFailed"] = "Failed to create dungeon entity.",
["DungeonRemoved"] = "Dungeon at {0} no longer exists. Cleaning up.",
["DungeonSpawning"] = "Spawning dungeon...",
["LootBoxFilled"] = "Loot box filled with items.",
["AutoSpawnEnabled"] = "Auto-spawning dungeons every {0} seconds.",
},
this
);
}
private string Msg(string userId, string key, params object[] args)
{
return string.Format(lang.GetMessage(key, this, userId), args);
}
#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
}
}
ASSISTANT
The error you are encountering (`FormatException: Index (zero based) must be greater than or equal to zero and less than the size of the argument list.`) indicates that there is a problem with the way placeholders in your string are being used compared to the arguments provided. Here’s how you can identify and fix the issue:
### Key Steps to Resolve the Issue
1. **Review the Message Format String**:
- Ensure that the `DungeonAppeared` message has exactly two placeholders (`{0}` for `tierName` and `{1}` for `gridPosition`).
2. **Inspect the Call to `Msg` Method**:
- Verify that **two** arguments are being passed correctly to match the two placeholders ({0} and {1}).
Let's revisit the specific code around message retrieval and usage, and ensure everything is correctly aligned:
### Code Review and Corrections
1. **Message Definition in `LoadDefaultMessages`:**
Your message definitions seem correct given your `DungeonAppeared` entry. Here is the relevant part:
```csharp
["DungeonAppeared"] = "A {0} Dungeon has appeared at {1}!",
```
Ensure this matches the number of arguments being passed.
2. **Method Usage:**
In `NotifyPlayersOfDungeonLocation`, you have:
```csharp
private void NotifyPlayersOfDungeonLocation(Vector3 position, string tierName)
{
string gridPosition = PhoneController.PositionToGridCoord(position);
foreach (var player in BasePlayer.activePlayerList)
{
string message = Msg(player.UserIDString, "DungeonAppeared", tierName, gridPosition);
Puts($"Formatted Message: {message}");
//player.ChatMessage(message);
//player.ShowToast(GameTip.Styles.Blue_Normal, message);
}
}
```
This snippet shows that you're calling the message with the correct arguments: `tierName` and `gridPosition`, which should match `{0}` and `{1}` in your message string.
3. **Common Issues and Checks**:
- **Misalignment**: Ensure the `tierName` and `gridPosition` are correctly passed and not altered elsewhere before reaching the `Msg` call.
- **Ensure Correct Message Key**: Verify you're using the exact key registered in `LoadDefaultMessages` (`DungeonAppeared`).
- **Resetting Cached Messages**: Sometimes plugins can have old message caches. Ensure your changes to messages are applied by reloading the plugin and possibly restarting the server.
- **Language Files**: Confirm there are no overriding language JSON files with different placeholders.
4. **Debugging Check**:
Given the function seems correctly constructed, add a fall-through check in your `Msg` method to catch misalignments:
```csharp
private string Msg(string userId, string key, params object[] args)
{
try
{
return string.Format(lang.GetMessage(key, this, userId), args);
}
catch (FormatException ex)
{
PrintError($"Formatting error in message key {key}. Arguments provided: {args.Length}. Expected format: {lang.GetMessage(key, this)}");
return $"[Error: {ex.Message}]";
}
}
```
This additional debug information will further guide where the misalignment is occurring by specifying the issue during runtime.
5. **Trigger Plugin Commands**:
After making sure all the above steps are correct, run commands like `dun` or actions that would trigger this function to see if the debugging reveals more accurate information.
If the error persists and the above checks do not resolve it, verify all your external configurations and consider obtaining a clean server environment to ensure no other scripts or data is influencing behavior.