C#:
// Все права принадлежат дискорд сообществу https://discord.gg/VgNHPpNrz6
using System;
using System.Collections;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using Facepunch;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using Oxide.Core;
using Oxide.Core.Plugins;
using Oxide.Game.Rust.Cui;
using Rust;
using UnityEngine;
using Random = UnityEngine.Random;
namespace Oxide.Plugins
{
[Info("Dungeon Events", "Marte6", "3.5.2")]
[Description("Create dungeons populated with NPCs, turrets and loot containers.")]
public class DungeonEvents : RustPlugin
{
[PluginReference]
private Plugin NpcSpawn = null,
Friends = null,
Economics = null,
ServerRewards = null,
Notify = null,
NightVision = null,
ZoneManager = null,
Duelist = null,
RaidableBases = null,
AbandonedBases = null,
RestoreUponDeath = null,
SkillTree = null;
private static DungeonEvents _instance;
private Configuration _config;
private List<ActiveDungeon> _activeDungeons = new List<ActiveDungeon>();
private List<BasePlayer> _playersBuying = new List<BasePlayer>();
private Dictionary<ulong, DateTime> _buyCooldowns = new Dictionary<ulong, DateTime>();
private List<DungeonMarker> _activeDungeonMarkers = new List<DungeonMarker>();
private Queue<DungeonRequest> _dungeonQueue = new Queue<DungeonRequest>();
private Queue<DungeonRequest> _dungeonFailedAutoSpawnQueue = new Queue<DungeonRequest>();
private Queue<DungeonRequest> _dungeonFailedBuyQueue = new Queue<DungeonRequest>();
private Timer dungeonTimer;
private bool serverStarted = false;
private bool isCreatingDungeon = false;
private const bool ForceDefaultConfig = false;
private const float TimeTimers = 10f;
private const string PermissionAdmin = "dungeonevents.admin";
private const string PermissionBuy = "dungeonevents.buy";
private const string PermissionEnter = "dungeonevents.enter";
private const ulong OwnerID = 10203040505;
private const string UiEventPanel = "yFifW5s1zAY09h5cU0qeIQUNV6GGwevG";
private const string UiMainPanel = "5cU06GGweeIQ09hyFifW5qvGVsUNAY1z";
private const string UiCommandClose = "lDM82GJH3biLcI95ic8zzteteOeQx77h";
private Dictionary<DungeonTierConfig, TierInfo> _tierInfoMap = new Dictionary<DungeonTierConfig, TierInfo>();
private readonly string[] filesToDelete = new string[] { "Dungeon_01.json", "Dungeon_02.json", "Dungeon_03.json", "Dungeon_001.json", "Dungeon_002.json", "Dungeon_003.json" };
private static readonly WaitForSeconds SmallDelay = new WaitForSeconds(0.15f);
private static readonly WaitForSeconds BigDelay = new WaitForSeconds(0.50f);
private class SpawnedEntitiesInfo
{
public List<ulong> EntityIDs { get; set; } = new List<ulong>();
}
private void Init()
{
_instance = this;
permission.RegisterPermission(PermissionEnter, this);
permission.RegisterPermission(PermissionAdmin, this);
permission.RegisterPermission(PermissionBuy, this);
}
private void OnServerInitialized()
{
if (BasePlayer.activePlayerList.Count > 0)
{
serverStarted = true;
Start();
}
}
private void Unload()
{
dungeonTimer?.Destroy();
_dungeonQueue.Clear();
DestroyAllUis();
RemoveAllDungeons();
}
private void OnPlayerConnected(BasePlayer player)
{
if (serverStarted)
return;
serverStarted = true;
Start();
}
object OnEntityTakeDamage(BuildingBlock block, HitInfo info)
{
if (!IsDungeonEntity(block))
return null;
info.damageTypes?.ScaleAll(0f);
return true;
}
private object CanDropActiveItem(BasePlayer player)
{
if (RestoreUponDeath != null)
{
return null;
}
if (player == null)
return null;
if (GetDungeonForPlayer(player) != null)
{
var activeItem = player.GetActiveItem();
if (activeItem != null)
return false;
}
return null;
}
private object CanPickupEntity(BasePlayer player, BaseEntity entity)
{
if (player == null)
return null;
if (GetDungeonForPlayer(player) != null)
{
return false;
}
return null;
}
object CanDeployItem(BasePlayer player, Deployer deployer, ulong entityId)
{
if (player == null)
return null;
var dungeon = GetDungeonForPlayer(player);
if (dungeon != null)
{
return false;
}
return null;
}
object CanBuild(Planner planner, Construction prefab, Construction.Target target)
{
var player = planner?.GetOwnerPlayer();
if (player == null)
return null;
if (GetDungeonForPlayer(player) != null)
return false;
Vector3 buildPos = target.position;
if (buildPos == Vector3.zero && target.socket != null && target.entity != null)
buildPos = target.GetWorldPosition();
float r = _config.RadiusSphere;
float r2 = r * r;
foreach (var dungeon in _activeDungeons)
{
if (dungeon.PortalPosition == Vector3.zero)
continue;
float dx = buildPos.x - dungeon.PortalPosition.x;
float dz = buildPos.z - dungeon.PortalPosition.z;
if (dx * dx + dz * dz <= r2)
return false;
}
return null;
}
private object OnPlayerCommand(BasePlayer player, string command, string[] args)
{
if (player == null)
return null;
if (GetDungeonForPlayer(player) == null)
return null;
if (_config.BlockCommands.Contains("/" + command.ToLower()))
{
NotifyPlayer(player, "BlockedCommands", 0);
return true;
}
return null;
}
private object OnServerCommand(ConsoleSystem.Arg arg)
{
if (arg == null || arg.cmd == null)
return null;
BasePlayer player = arg.Player();
if (player != null && (GetDungeonForPlayer(player) != null))
{
if (_config.BlockCommands.Contains(arg.cmd.Name.ToLower()) || _config.BlockCommands.Contains(arg.cmd.FullName.ToLower()))
{
NotifyPlayer(player, "BlockedCommands", 0);
return true;
}
}
return null;
}
private object OnEntityTakeDamage(ScientistNPC scientistNPC, HitInfo info)
{
if (scientistNPC == null || info?.Initiator == null)
return null;
if (info.Initiator is AutoTurret turret && IsDungeonEntity(turret))
{
info.damageTypes?.ScaleAll(0f);
}
return null;
}
private object CanEntityTakeDamage(BasePlayer victim, HitInfo info)
{
if (info?.Initiator == null || info.Initiator.net == null || !IsPlayer(victim))
return null;
if (info.Initiator is ScientistNPC && IsDungeonEntity(info.Initiator))
return true;
if (info.Initiator is AutoTurret && IsDungeonEntity(info.Initiator))
return true;
return null;
}
private object CanEntityTakeDamage(AutoTurret autoTurret, HitInfo hitinfo)
{
if (autoTurret == null || hitinfo == null)
return null;
if (!IsDungeonEntity(autoTurret))
return null;
if (hitinfo.InitiatorPlayer == null || !IsPlayer(hitinfo.Initiator as BasePlayer))
return false;
return true;
}
private object CanEntityTakeDamage(StorageContainer storageContainer, HitInfo hitinfo)
{
if (storageContainer == null || hitinfo == null)
return null;
if (!IsDungeonEntity(storageContainer))
return null;
if (hitinfo.InitiatorPlayer == null || !IsPlayer(hitinfo.Initiator as BasePlayer))
return false;
return true;
}
private object CanEntityTakeDamage(Door victim, HitInfo hitinfo)
{
if (victim == null || hitinfo == null)
return null;
if (!IsDungeonEntity(victim))
return null;
if (hitinfo.InitiatorPlayer == null || !IsPlayer(hitinfo.Initiator as BasePlayer))
return false;
return true;
}
private object OnCustomNpcTarget(ScientistNPC scientistNPC, BasePlayer player)
{
if (scientistNPC == null || !IsDungeonEntity(scientistNPC))
return null;
if (player == null || !player.userID.IsSteamId())
return false;
return null;
}
private object CanEntityBeTargeted(BasePlayer player, AutoTurret turret)
{
if (turret == null || !IsDungeonEntity(turret))
return null;
if (player == null || !player.userID.IsSteamId())
return false;
return true;
}
private object OnEntityEnter(TargetTrigger trigger, ScientistNPC scientistNPC)
{
if (trigger == null || scientistNPC == null || scientistNPC.net == null || scientistNPC.userID.IsSteamId())
return null;
AutoTurret autoTurret = trigger.GetComponentInParent<AutoTurret>();
if (autoTurret == null || autoTurret.net == null)
return null;
if (IsDungeonEntity(autoTurret))
return true;
return null;
}
private object OnPortalUse(BasePlayer player, BasePortal portal)
{
var activeDungeon = GetActiveDungeonByPortal(portal);
if (activeDungeon == null)
return null;
if (!activeDungeon.Spawned)
{
NotifyPlayer(player, "DungeonStillSpawningMessage", 0);
return false;
}
if (_config.LockDungeonToPlayer && !CanEnter(player, activeDungeon))
{
NotifyPlayer(player, "NoAccess", 0);
return false;
}
return null;
}
public class CorpseLootData : MonoBehaviour
{
public bool IsBoss;
public NpcConfig LootConfig;
}
private void OnCorpsePopulate(ScientistNPC npc, NPCPlayerCorpse corpse)
{
if (npc == null || corpse == null)
return;
if (!IsDungeonEntity(npc))
return;
var activeDungeon = GetActiveDungeonByEntity(npc);
if (activeDungeon == null)
return;
bool isBoss = npc.GetComponent<BossMonoBehaviour>() != null;
var lootConfig = isBoss ? activeDungeon.TierConfig?.BossNpcConfig : activeDungeon.TierConfig?.NpcConfig;
var corpseData = corpse.gameObject.AddComponent<CorpseLootData>();
corpseData.IsBoss = isBoss;
corpseData.LootConfig = lootConfig;
NextTick(() =>
{
if (corpse == null)
return;
var storedData = corpse.GetComponent<CorpseLootData>();
if (storedData == null || storedData.LootConfig == null)
return;
Effect.server.Run("assets/prefabs/weapons/flashbang/effects/fx-flashbang-boom.prefab", corpse.transform.position);
var container = corpse.containers[0];
container.Clear();
var items = storedData.LootConfig.LootDropConfig?.Items;
if (items != null && items.Count > 0)
{
var lootItems = items.OrderBy(_ => Random.value).ToList();
foreach (var itemCfg in lootItems)
{
if (Random.value * 100f <= itemCfg.InclusionChancePercentage)
{
var def = ItemManager.FindItemDefinition(itemCfg.ShortName);
if (def == null)
continue;
int amount = Random.Range(itemCfg.MinimumAmount, itemCfg.MaximumAmount + 1);
if (container.itemList.Count >= container.capacity)
break;
var item = ItemManager.Create(def, amount, itemCfg.SkinID);
if (!string.IsNullOrEmpty(itemCfg.CustomName))
item.name = itemCfg.CustomName;
item.MoveToContainer(container);
}
}
}
});
}
private void OnPlayerDeath(BasePlayer player, HitInfo info)
{
if (player == null || !player.userID.IsSteamId())
return;
ActiveDungeon dungeon;
try
{
dungeon = GetDungeonForPlayer(player);
}
catch (Exception)
{
return;
}
if (dungeon == null)
return;
var basePortal = dungeon.BasePortal;
var insidePortal = dungeon.InsidePortal;
if (basePortal == null || insidePortal == null)
return;
if (basePortal.transform == null || insidePortal.transform == null)
return;
try
{
TeleportPlayerToOutsidePortal(player, dungeon);
}
catch (Exception) { }
if (dungeon.Entities == null)
return;
Vector3 exitPos;
try
{
exitPos = insidePortal.transform.position;
}
catch
{
return;
}
foreach (var door in dungeon.Entities.OfType<Door>())
{
if (door == null || door.IsDestroyed || door.transform == null)
continue;
try
{
if (Vector3.Distance(door.transform.position, exitPos) <= 3f)
{
door.SetOpen(false);
door.SendNetworkUpdate();
}
}
catch (Exception) { }
}
}
object CanRaidBlock(BasePlayer player)
{
var dungeon = GetDungeonForPlayer(player);
if (dungeon != null)
return false;
return null;
}
private void OnEntityDeath(BaseCombatEntity entity, HitInfo info)
{
if (entity == null || info?.InitiatorPlayer == null)
return;
ActiveDungeon dungeon = GetActiveDungeonByEntity(entity);
if (dungeon == null || !dungeon.Spawned)
return;
bool isNpc = entity is ScientistNPC;
bool isTurret = entity is AutoTurret;
bool isBox = entity is StorageContainer;
if (!isNpc && !isTurret && !isBox)
return;
RewardConfig r = dungeon.TierConfig?.RewardConfig;
if (r == null)
return;
int econ =
isNpc ? r.NpcEconomicsReward
: isTurret ? r.TurretEconomicsReward
: r.BoxEconomicsReward;
int sr =
isNpc ? r.NpcServerRewards
: isTurret ? r.TurretServerRewards
: r.BoxServerRewards;
BasePlayer attacker = info.InitiatorPlayer;
if (econ > 0 && Economics != null)
{
Economics.Call("Deposit", attacker.userID, (double)econ);
attacker.ChatMessage(Msg(attacker.UserIDString, "EconRewardMessage", econ));
}
if (sr > 0 && ServerRewards != null)
{
ServerRewards.Call("AddPoints", attacker.userID, sr);
attacker.ChatMessage(Msg(attacker.UserIDString, "ServerRewardMessage", sr));
}
}
private void SaveDungeonEntities(string dungeonId, List<ulong> entityIds)
{
var file = Interface.Oxide.DataFileSystem.GetFile($"{Name}/{dungeonId}");
file.WriteObject(entityIds);
}
private List<ulong> LoadDungeonEntities(string dungeonId)
{
var file = Interface.Oxide.DataFileSystem.GetFile($"{Name}/{dungeonId}");
return file.ReadObject<List<ulong>>() ?? new List<ulong>();
}
private void DeleteDungeonEntitiesFile(string dungeonId)
{
string folder = Path.Combine(Interface.Oxide.DataFileSystem.Directory, Name);
if (!Directory.Exists(folder))
return;
string filePath = Path.Combine(folder, $"{dungeonId}.json");
if (File.Exists(filePath))
{
try
{
File.Delete(filePath);
}
catch (Exception ex)
{
PrintError("Error deleting dungeon file: " + ex.Message);
}
}
}
private void RegisterSpawnedEntityForDungeon(BaseEntity entity, string dungeonId)
{
if (entity != null && entity.net != null)
{
List<ulong> ids = LoadDungeonEntities(dungeonId);
ids.Add(entity.net.ID.Value);
SaveDungeonEntities(dungeonId, ids);
}
}
private void RemoveEntitiesForDungeon(string dungeonId)
{
List<ulong> ids = LoadDungeonEntities(dungeonId);
foreach (ulong id in ids)
{
BaseEntity entity = BaseNetworkable.serverEntities.Find(new NetworkableId(id)) as BaseEntity;
if (entity != null && !entity.IsDestroyed)
entity.Kill();
}
DeleteDungeonEntitiesFile(dungeonId);
}
private void RemoveLeftoverDungeonFiles()
{
string folder = Path.Combine(Interface.Oxide.DataFileSystem.Directory, Name);
if (!Directory.Exists(folder))
return;
foreach (var file in Directory.GetFiles(folder, "*.json"))
{
try
{
string fileName = Path.GetFileNameWithoutExtension(file);
RemoveEntitiesForDungeon(fileName);
}
catch (Exception ex)
{
PrintError("Error removing leftover dungeon file: " + ex.Message);
}
}
ServerMgr.Instance.StartCoroutine(CleanupExistingEntities());
}
private void DeleteDungeonFiles()
{
string pluginDataFolder = Path.Combine(Interface.Oxide.DataDirectory, Name);
if (!Directory.Exists(pluginDataFolder))
{
return;
}
foreach (var fileName in filesToDelete)
{
string filePath = Path.Combine(pluginDataFolder, fileName);
if (File.Exists(filePath))
{
try
{
File.Delete(filePath);
}
catch (Exception) { }
}
}
}
private Vector3 GetCenterFoundationPositionNearPlayer(BasePlayer player, float portalHeight)
{
const float searchRadius = 8f;
const float minDistance = 2f;
const float playerClearance = 1.5f;
Vector3 selected = Vector3.zero;
float bestDist = float.MaxValue;
List<BuildingBlock> blocks = Pool.GetList<BuildingBlock>();
Vis.Entities(player.transform.position, searchRadius, blocks, Layers.Mask.Construction);
foreach (var block in blocks)
{
if (block == null)
continue;
if (!block.PrefabName.Contains("foundation"))
continue;
float dist = Vector3.Distance(block.transform.position, player.transform.position);
if (dist < minDistance)
continue;
List<BasePlayer> nearPlayers = Pool.GetList<BasePlayer>();
Vis.Entities(block.transform.position, playerClearance, nearPlayers, Layers.Mask.Player_Server);
bool hasPlayer = nearPlayers.Count > 0;
Pool.FreeList(ref nearPlayers);
if (hasPlayer)
continue;
if (dist < bestDist)
{
bestDist = dist;
selected = block.transform.position;
}
}
if (selected == Vector3.zero)
{
foreach (var block in blocks)
{
if (block == null)
continue;
if (!block.PrefabName.Contains("foundation"))
continue;
List<BasePlayer> nearPlayers = Pool.GetList<BasePlayer>();
Vis.Entities(block.transform.position, playerClearance, nearPlayers, Layers.Mask.Player_Server);
bool hasPlayer = nearPlayers.Count > 0;
Pool.FreeList(ref nearPlayers);
if (hasPlayer)
continue;
float dist = Vector3.Distance(block.transform.position, player.transform.position);
if (dist < bestDist)
{
bestDist = dist;
selected = block.transform.position;
}
}
}
Pool.FreeList(ref blocks);
if (selected == Vector3.zero)
return Vector3.zero;
return new Vector3(selected.x, portalHeight, selected.z);
}
private void RewardDungeonClear(ActiveDungeon dungeon)
{
int econReward = dungeon.TierConfig.RewardConfig.DungeonClearEconomicsReward;
int srReward = dungeon.TierConfig.RewardConfig.DungeonClearServerRewards;
double xpReward = dungeon.TierConfig.RewardConfig.DungeonClearXpReward;
bool rewarded = false;
if (dungeon.OwnerID != 0UL)
{
var owner = BasePlayer.FindByID(dungeon.OwnerID);
if (owner != null && owner.IsConnected)
{
try
{
if (econReward > 0 && Economics != null)
{
Economics.Call("Deposit", owner.userID, (double)econReward);
owner.ChatMessage(Msg(owner.UserIDString, "DungeonClearEconomyReward", econReward));
}
if (srReward > 0 && ServerRewards != null)
{
ServerRewards.Call("AddPoints", owner.userID, srReward);
owner.ChatMessage(Msg(owner.UserIDString, "DungeonClearServerReward", srReward));
}
if (xpReward > 0.0 && SkillTree != null)
{
SkillTree.Call("AwardXP", owner.userID, xpReward, "DungeonEvents", false);
owner.ChatMessage(Msg(owner.UserIDString, "DungeonClearXpReward", xpReward));
}
_buyCooldowns[owner.userID] = DateTime.UtcNow.AddSeconds(_config.DungeonSpawn.BuyCooldown);
rewarded = true;
}
catch
{
rewarded = false;
}
}
}
if (!rewarded)
{
foreach (ulong uid in dungeon.PlayersInside.ToList())
{
var participant = BasePlayer.FindByID(uid);
if (participant != null && participant.IsConnected)
{
try
{
if (econReward > 0 && Economics != null)
{
Economics.Call("Deposit", participant.userID, (double)econReward);
participant.ChatMessage(Msg(participant.UserIDString, "DungeonClearEconomyReward", econReward));
}
if (srReward > 0 && ServerRewards != null)
{
ServerRewards.Call("AddPoints", participant.userID, srReward);
participant.ChatMessage(Msg(participant.UserIDString, "DungeonClearServerReward", srReward));
}
if (xpReward > 0.0 && SkillTree != null)
{
SkillTree.Call("AwardXP", participant.userID, xpReward, "DungeonEvents", false);
participant.ChatMessage(Msg(participant.UserIDString, "DungeonClearXpReward", xpReward));
}
_buyCooldowns[participant.userID] = DateTime.UtcNow.AddSeconds(_config.DungeonSpawn.BuyCooldown);
break;
}
catch { }
}
}
}
string redLightPrefab = "assets/bundled/prefabs/modding/cinematic/cinelights/cinelight_point_red.prefab";
string greenLightPrefab = "assets/bundled/prefabs/modding/cinematic/cinelights/cinelight_point_green.prefab";
foreach (var entity in dungeon.Entities.ToList())
{
if (entity != null && entity.PrefabName == redLightPrefab)
{
Vector3 pos = entity.transform.position;
Quaternion rot = entity.transform.rotation;
entity.Kill();
var newLight = GameManager.server.CreateEntity(greenLightPrefab, pos, rot);
if (newLight != null)
{
newLight.Spawn();
dungeon.Entities.Add(newLight);
RegisterSpawnedEntityForDungeon(newLight, dungeon.UniqueId);
}
}
}
foreach (var entity in dungeon.Entities.ToList())
{
if (entity == null)
continue;
if (entity is Door || entity.PrefabName.Contains("door"))
{
entity.Kill();
dungeon.Entities.Remove(entity);
}
}
dungeon.HasBeenCleared = true;
dungeon.DestructionDelay = _config.DungeonSpawn.DungeonRemoval.RemovalAfterDestroyedTimer;
dungeon.DestructionCountdownStart = DateTime.UtcNow;
if (dungeon.InsidePortal != null && !dungeon.InsidePortal.IsDestroyed)
{
BasePlayer targetPlayer = null;
foreach (ulong uid in dungeon.PlayersInside)
{
var p = BasePlayer.FindByID(uid);
if (p != null && p.IsConnected)
{
targetPlayer = p;
break;
}
}
if (targetPlayer == null && dungeon.OwnerID != 0UL)
targetPlayer = BasePlayer.FindByID(dungeon.OwnerID);
if (targetPlayer != null)
{
Vector3 centerPos = GetCenterFoundationPositionNearPlayer(targetPlayer, dungeon.InsidePortal.transform.position.y);
if (centerPos == Vector3.zero)
{
Vector3 fallback = targetPlayer.transform.position + targetPlayer.transform.right * 2f;
fallback.y = dungeon.InsidePortal.transform.position.y;
dungeon.InsidePortal.transform.position = fallback;
}
else
{
dungeon.InsidePortal.transform.position = centerPos;
}
dungeon.InsidePortal.SendNetworkUpdateImmediate();
}
}
}
private void TeleportPlayersAndDestroyDungeon(ActiveDungeon dungeon)
{
foreach (ulong userID in dungeon.PlayersInside.ToList())
{
BasePlayer player = BasePlayer.FindByID(userID);
TeleportPlayerToOutsidePortal(player, dungeon);
player.ChatMessage(Msg(player?.UserIDString, "Dungeonclosed"));
}
DestroyDungeon(dungeon);
}
private void TeleportPlayerToOutsidePortal(BasePlayer player, ActiveDungeon dungeon)
{
if (player == null || !player.IsConnected)
return;
Vector3 exitPos = (dungeon.BasePortal != null && dungeon.BasePortal.transform != null) ? dungeon.BasePortal.transform.position : dungeon.PortalPosition;
List<ulong> playersInside = dungeon.PlayersInside.ToList();
playersInside.Sort();
int count = playersInside.Count;
int index = playersInside.IndexOf(player.userID);
if (index < 0)
index = 0;
float angleIncrement = 360f / Mathf.Max(count, 1);
float angle = angleIncrement * index;
float rad = angle * Mathf.Deg2Rad;
float distance = 10f;
Vector3 horizontalOffset = new Vector3(Mathf.Cos(rad), 0f, Mathf.Sin(rad)) * distance;
Vector3 verticalOffset = Vector3.up * 5f;
Vector3 newPos = exitPos + horizontalOffset + verticalOffset;
player.Teleport(newPos);
dungeon.PlayersInside.Remove(player.userID);
UpdateTime(player, -1);
DestroyEventUi(player);
player.limitNetworking = false;
player.SendNetworkUpdateImmediate();
}
[ConsoleCommand("buydungeon")]
private void BuyDungeonConsoleCommand(ConsoleSystem.Arg arg)
{
if (arg == null || !arg.HasArgs(2))
{
LogDebug("Usage: buydungeon <tierName> <playerID>", 1);
return;
}
string tierName = arg.GetString(0);
string playerId = arg.GetString(1);
if (string.IsNullOrEmpty(tierName) || string.IsNullOrEmpty(playerId))
return;
BasePlayer player = BasePlayer.FindAwakeOrSleeping(playerId);
if (player == null)
{
LogDebug($"Player with ID '{playerId}' not found.", 1);
return;
}
ProcessBuyDungeon(player, tierName);
}
[ConsoleCommand(UiCommandClose)]
private void CloseCommandUi(ConsoleSystem.Arg arg)
{
var player = arg.Player();
if (player != null)
DestroyUi(player);
}
[ChatCommand("buydungeon")]
private void BuyDungeonChatCommandUi(BasePlayer player, string command, string[] args)
{
if (player == null)
return;
if (!permission.UserHasPermission(player.UserIDString, PermissionBuy))
{
NotifyPlayer(player, "NoAccess", 0);
return;
}
if (args != null && args.Length > 0)
{
ProcessBuyDungeon(player, args[0]);
return;
}
if (!_config.EnablePurchases)
{
NotifyPlayer(player, "PurchasesDisabled", 0);
return;
}
OpenBuyDungeonGUI(player);
}
[ChatCommand("createdungeon")]
private void CreateDungeonCommand(BasePlayer player, string command, string[] args)
{
if (!IsAdmin(player))
return;
if (args.Length == 0)
{
NotifyPlayer(player, "UsageCreateMessage", 0);
return;
}
string tierName = args[0].ToLower();
var prop = _config.Tiers.GetType().GetProperties().FirstOrDefault(p => p.Name.Equals(tierName, StringComparison.OrdinalIgnoreCase));
var tier = prop?.GetValue(_config.Tiers) as DungeonTierConfig;
if (tier == null)
{
NotifyPlayer(player, "InvalidTierMessage", 0, tierName, GetAvailableTiers());
return;
}
if (!tier.Enabled)
{
NotifyPlayer(player, "ThisTierDisabled", 0);
return;
}
LogDebug("Received dungeon creation request.", 1);
EnqueueDungeonRequest(player, tier, false);
}
[ChatCommand("de.reloadconfig")]
private void ReloadConfigCommand(BasePlayer player, string command, string[] args)
{
if (!IsAdmin(player))
return;
LoadConfig();
dungeonTimer?.Destroy();
dungeonTimer = timer.Every(TimeTimers, HandleDungeonTimers);
UpdateMarkers();
_dungeonQueue.Clear();
NotifyPlayer(player, "ConfigReloaded", 0);
}
[ChatCommand("de.toggle")]
private void TogglePurchasesCommand(BasePlayer player, string command, string[] args)
{
if (!IsAdmin(player))
return;
_config.EnablePurchases = !_config.EnablePurchases;
if (_config.EnablePurchases)
NotifyPlayer(player, "PurchasesEnabled", 0);
else
NotifyPlayer(player, "PurchasesDisabled", 0);
}
[ChatCommand("removeinactivedungeons")]
private void RemoveInactiveDungeonsCommand(BasePlayer player, string command, string[] args)
{
if (!IsAdmin(player))
return;
var removedDungeons = RemoveInactiveDungeons();
NotifyPlayerOnRemoval(player, removedDungeons);
}
[ChatCommand("removealldungeons")]
private void RemoveAllDungeonsCommand(BasePlayer player, string command, string[] args)
{
if (!IsAdmin(player))
return;
var removed = RemoveAllDungeons();
NotifyPlayerOnRemoval(player, removed);
}
[ChatCommand("de.removenearest")]
private void RemoveNearestDungeonCommand(BasePlayer player, string command, string[] args)
{
if (!IsAdmin(player))
return;
if (_activeDungeons.Count == 0)
{
NotifyPlayer(player, "NoDungeonsToRemove", 0);
return;
}
ActiveDungeon nearest = null;
float minDist = float.MaxValue;
foreach (var dungeon in _activeDungeons)
{
float d1 = Vector3.Distance(player.transform.position, dungeon.PortalPosition);
float d2 = Vector3.Distance(player.transform.position, dungeon.DungeonPosition);
float d = Mathf.Min(d1, d2);
if (d < minDist)
{
minDist = d;
nearest = dungeon;
}
}
if (nearest == null || minDist > 10f)
{
NotifyPlayer(player, "TooFarToRemove", 0);
return;
}
DestroyDungeon(nearest);
NotifyPlayer(player, "DungeonRemovedDetail", 0, nearest.TierName, nearest.Grid);
}
[ChatCommand("removedungeon")]
private void RemoveOwnDungeonCommand(BasePlayer player, string command, string[] args)
{
if (player == null)
return;
var owned = _activeDungeons.FirstOrDefault(d => d.OwnerID == player.userID);
if (owned == null)
{
NotifyPlayer(player, "NotDungeonOwner", 0);
return;
}
DestroyDungeon(owned);
NotifyPlayer(player, "DungeonRemovedDetail", 0, owned.TierName, owned.Grid);
}
[ChatCommand("forceremovealldungeons")]
private void ForceRemoveAllDungeonsCommand(BasePlayer player, string command, string[] args)
{
if (!IsAdmin(player))
return;
CleanupExistingEntities();
NotifyPlayer(player, "CleanedUp", 0);
}
[ChatCommand("spawdungeonnentity")]
private void SpawnDungeonEntityCommand(BasePlayer player, string command, string[] args)
{
if (!IsAdmin(player))
{
NotifyPlayer(player, "NoAccess", 0);
return;
}
if (args.Length < 2)
{
player.ChatMessage("Correct Usage: /spawdungeonnentity <npc|boss|turret> <easy|normal|medium|hard|nightmare>");
return;
}
var entityType = args[0].ToLower();
var difficultyName = args[1].ToLower();
DungeonTierConfig tierConfig = null;
var allTiersProps = _config.Tiers.GetType().GetProperties();
var foundTierProp = allTiersProps.FirstOrDefault(p => p.Name.Equals(difficultyName, StringComparison.OrdinalIgnoreCase));
if (foundTierProp == null)
{
player.ChatMessage("Invalid difficulty.Try: easy, normal, medium, hard, nightmare.");
return;
}
tierConfig = foundTierProp.GetValue(_config.Tiers) as DungeonTierConfig;
if (tierConfig == null)
{
player.ChatMessage("Failed to retrieve difficulty settings.");
return;
}
var spawnPos = player.transform.position + (player.eyes.BodyForward() * 2f);
var spawnRot = Quaternion.Euler(0f, player.eyes.rotation.eulerAngles.y, 0f);
var dummyDungeon = new ActiveDungeon
{
Entities = new List<BaseEntity>(),
Npcs = new List<BaseEntity>(),
TierConfig = tierConfig,
};
switch (entityType)
{
case "npc":
SpawnNpc(spawnPos, dummyDungeon, boss: false);
player.ChatMessage($"NPC ({difficultyName}) spawned in front of you!");
break;
case "boss":
SpawnNpc(spawnPos, dummyDungeon, boss: true);
player.ChatMessage($"Boss ({difficultyName}) spawned in front of you!");
break;
case "turret":
SpawnTurret(spawnPos, spawnRot, dummyDungeon.TierConfig.AutoTurretConfig, dummyDungeon);
player.ChatMessage($"Turret ({difficultyName}) spawned in front of you!");
break;
default:
player.ChatMessage("Invalid type! Use /spawdungeonnentity <npc|boss|turret> <easy|normal|medium|hard|nightmare>");
break;
}
}
[ConsoleCommand("spawnrandomdungeon")]
private void SpawnRandomDungeonConsoleCommand(ConsoleSystem.Arg arg)
{
if (arg.Player() != null && !IsAdmin(arg.Player()))
{
arg.ReplyWith("You do not have permission to run this command.");
return;
}
var tier = GetDifficultyWithLeastActiveDungeons();
EnqueueDungeonRequest(null, tier, false);
arg.ReplyWith($"Enqueued random dungeon spawn of tier: {_tierInfoMap[tier].Name} (no owner).");
}
[ConsoleCommand("spawnfixeddungeon")]
private void SpawnFixedDungeonConsoleCommand(ConsoleSystem.Arg arg)
{
if (arg.Player() != null && !IsAdmin(arg.Player()))
{
arg.ReplyWith("You do not have permission to run this command.");
return;
}
if (!arg.HasArgs(1))
{
arg.ReplyWith("Usage: spawnfixeddungeon <tierName>");
return;
}
var tierName = arg.GetString(0).ToLower();
var tier = _config.Tiers.GetType().GetProperties().FirstOrDefault(prop => prop.Name.ToLower() == tierName)?.GetValue(_config.Tiers) as DungeonTierConfig;
if (tier == null)
{
arg.ReplyWith($"Invalid tier '{tierName}'.Available tiers: {GetAvailableTiers()}");
return;
}
if (!_tierInfoMap.ContainsKey(tier))
{
arg.ReplyWith("This tier is disabled.");
return;
}
EnqueueDungeonRequest(null, tier, false);
arg.ReplyWith($"Enqueued dungeon spawn of tier: {_tierInfoMap[tier].Name} (no owner).");
}
public class DungeonRequest
{
public BasePlayer Player { get; set; }
public DungeonTierConfig Tier { get; set; }
public bool LockToPlayer { get; set; }
public DungeonOrigin Origin { get; set; }
public Vector3 PortalPosition { get; set; }
public Vector3 DungeonPosition { get; set; }
}
private IEnumerator CreateDungeon(DungeonRequest request)
{
yield return null;
isCreatingDungeon = true;
var tierConfig = request.Tier;
var player = request.Player;
bool lockDungeonToPlayer = request.LockToPlayer;
var origin = request.Origin;
if (NpcSpawn == null)
{
LogDebug("NpcSpawn plugin is not loaded.", 2);
player?.ChatMessage(Msg(player?.UserIDString, "NpcSpawnNotLoaded"));
_playersBuying.Remove(player);
isCreatingDungeon = false;
yield break;
}
Vector3 portalPosition = Vector3.zero;
Vector3 dungeonPosition = Vector3.zero;
LogDebug("Generating portal spawn point.", 1);
var configPortal = new GeneratorConfig
{
Origin = player != null ? player.transform.position : Vector2.zero,
ClosestToOrigin = player != null,
YOffset = 0f,
minDist = player != null ? 10 : 200,
blocked = _activeDungeons.Select(d => d.PortalPosition).ToList(),
AvoidTerrainTopology = true,
AvoidedTopology =
TerrainTopology.Enum.Building
| TerrainTopology.Enum.Cliff
| TerrainTopology.Enum.Clutter
| TerrainTopology.Enum.Monument
| TerrainTopology.Enum.Ocean
| TerrainTopology.Enum.Rail
| TerrainTopology.Enum.Road,
AboveWater = true,
MaxSlopeDegrees = 30f,
CheckSphereRadius = 3f,
CheckSphereMask =
Layers.Mask.AI
| Layers.Mask.Bush
| Layers.Mask.Clutter
| Layers.Mask.Construction
| Layers.Mask.Default
| Layers.Mask.Deployed
| Layers.Mask.Harvestable
| Layers.Mask.Physics_Debris
| Layers.Mask.Player_Server
| Layers.Mask.Prevent_Building
| Layers.Mask.Prevent_Movement
| Layers.Mask.Tree
| Layers.Mask.Trigger
| Layers.Mask.Vehicle_Detailed
| Layers.Mask.Vehicle_Large
| Layers.Mask.Vehicle_World
| Layers.Mask.Water
| Layers.Mask.World
| Layers.Server.Deployed,
MaxAttempts = 1000,
UseSlopeCheck = true,
CheckSafeZones = true,
ZoneManager = true,
TerrainMask = Layers.Mask.Terrain | Layers.Mask.World | Layers.Mask.Construction,
CheckRocks = true,
RockCheck = RockKeywords,
};
yield return TryFindPosition(configPortal, request, true);
portalPosition = request.PortalPosition;
LogDebug("Generating dungeon spawn point.", 1);
var configDungeon = new GeneratorConfig
{
Origin = Vector2.zero,
ClosestToOrigin = false,
YOffset = 800f,
minDist = 200,
blocked = _activeDungeons.Select(d => d.DungeonPosition).ToList(),
AvoidTerrainTopology = true,
AvoidedTopology = TerrainTopology.Enum.Monument,
AboveWater = true,
MaxSlopeDegrees = 30f,
CheckSphereRadius = 100f,
CheckSphereMask =
Layers.Mask.AI
| Layers.Mask.Bush
| Layers.Mask.Clutter
| Layers.Mask.Construction
| Layers.Mask.Default
| Layers.Mask.Deployed
| Layers.Mask.Harvestable
| Layers.Mask.Physics_Debris
| Layers.Mask.Player_Server
| Layers.Mask.Prevent_Building
| Layers.Mask.Prevent_Movement
| Layers.Mask.Tree
| Layers.Mask.Trigger
| Layers.Mask.Vehicle_Detailed
| Layers.Mask.Vehicle_Large
| Layers.Mask.Vehicle_World
| Layers.Mask.Water
| Layers.Mask.World
| Layers.Server.Deployed,
MaxAttempts = 1000,
UseSlopeCheck = true,
CheckSafeZones = true,
ZoneManager = true,
TerrainMask = Layers.Mask.Terrain | Layers.Mask.World | Layers.Mask.Construction,
CheckRocks = true,
RockCheck = RockKeywords,
};
yield return TryFindPosition(configDungeon, request, false);
dungeonPosition = request.DungeonPosition;
if (portalPosition == Vector3.zero || dungeonPosition == Vector3.zero)
{
Requeue(request, origin);
LogDebug("Failed to find spawn points.Requeuing...", 1);
isCreatingDungeon = false;
yield break;
}
LogDebug("Dungeon spawn point found.");
// ... [Code preview truncated due to length. Full source in attached file] ...