C#:
// Все права принадлежат дискорд сообществу https://discord.gg/VgNHPpNrz6
/*
* < ----- End-User License Agreement ----->
*
* You may not copy, modify, merge, publish, distribute, sublicense, or sell copies of This Software without the Developer’s consent
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
* THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS
* BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE
* GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.*
* Developer: nivex (mswenson82@yahoo.com)
*
* Copyright © 2020-2023 nivex
*/
#pragma warning disable
using Facepunch;
using Network;
using Newtonsoft.Json;
using Newtonsoft.Json.Converters;
using Oxide.Core;
using Oxide.Core.Configuration;
using Oxide.Core.Libraries.Covalence;
using Oxide.Core.Plugins;
using Oxide.Game.Rust;
using Oxide.Game.Rust.Cui;
using Oxide.Plugins.RaidableBasesExtensionMethods;
using Rust;
using System;
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics;
using System.Globalization;
using System.IO;
using System.Text;
using System.Text.RegularExpressions;
using UnityEngine;
using UnityEngine.AI;
using UnityEngine.SceneManagement;
namespace Oxide.Plugins {
[Info("Raidable Bases", "nivex", "2.8.2")]
[Description("This plugin was fixed by Инкуб to order [Rust Plugin Sliv]: https://discord.gg/pFgKw6Dyyq")]
internal class RaidableBases : RustPlugin {
[PluginReference]
Plugin DangerousTreasures, ZoneManager, IQEconomic, Economics, ServerRewards, GUIAnnouncements, AdvancedAlerts, Archery, Space, Sputnik, PocketDimensions, FauxAdmin, Friends, Clans, Kits, TruePVE, NightLantern, Wizardry, NextGenPVE, Imperium, Backpacks, BaseRepair, Notify, SkillTree, ShoppyStock, BuyableBases;
private new const string Name = "RaidableBases";
private const int targetMask = 8454145;
private const int visibleMask = 10551553;
private const int targetMask2 = 10551313;
private const int manualMask = 1084293393;
private const int blockLayers = 2228480;
private const int queueLayers = 2228992;
internal ProtectionProperties _elevatorProtection;
internal ProtectionProperties _turretProtection;
public AutomatedController Automated;
public QueueController Queues;
private Coroutine setupCopyPasteObstructionRadius;
private float OceanLevel;
public List<RaidableBase> Raids = new();
public Dictionary<ulong, DelaySettings> PvpDelay = new();
public Dictionary<ulong, HumanoidBrain> HumanoidBrains = new();
private Dictionary<string, SkinInfo> Skins = new();
private Dictionary<NetworkableId, BMGELEVATOR> _elevators = new();
public StoredData data = new();
public TemporaryData temporaryData = new();
private StringBuilder _sb = new();
private StringBuilder _sb2 = new();
private bool wiped;
private bool buyableEnabled;
private bool IsUnloading;
private bool IsShuttingDown;
private bool bypassRestarting;
public BuildingTables Buildings = new();
private bool DebugMode;
private int despawnLimit = 10;
private Dictionary<string, ItemDefinition> DeployableItems = new();
private Dictionary<ItemDefinition, string> ItemDefinitions = new();
private Dictionary<string, ItemDefinition> _itemModEntity = new();
private List<string> ExcludedMounts = new() {
"beachchair", "boogieboard", "cardtable", "chair", "chippyarcademachine", "computerstation", "drumkit", "microphonestand", "piano", "secretlabchair", "slotmachine", "sofa", "xylophone"
}
;
private List<string> Blocks = new() {
"wall.doorway", "wall", "wall.frame", "wall.half", "wall.low", "wall.window", "foundation.triangle", "foundation", "wall.external.high.wood", "wall.external.high.stone", "wall.external.high.ice", "floor.triangle.frame", "floor.triangle", "floor.frame"
}
;
private List<string> TrueDamage = new() {
"spikes.floor", "barricade.metal", "barricade.woodwire", "barricade.wood", "wall.external.high.wood", "wall.external.high.stone", "wall.external.high.ice"
}
;
private List<string> arguments = new() {
"add", "remove", "list", "clean", "easy", "med", "medium", "hard", "expert", "nightmare", "0", "1", "2", "3", "4", "toggle", "stability", "maintained", "scheduled"
}
;
private SkinSettingsImportedWorkshop ImportedWorkshopSkins = new();
private const float M_RADIUS = 25f;
private const float CELL_SIZE = 12.5f;
private List<Coroutine> loadCoroutines = new();
private Dictionary<string, PasteData> _pasteData = new();
private readonly IPlayer _consolePlayer = new Game.Rust.Libraries.Covalence.RustConsolePlayer();
private List<BaseEntity.Slot> _checkSlots = new() {
BaseEntity.Slot.Lock, BaseEntity.Slot.UpperModifier, BaseEntity.Slot.MiddleModifier, BaseEntity.Slot.LowerModifier
}
;
public class PasteData {
public bool valid;
public float radius;
public List<Vector3> foundations;
public PasteData() {
}
}
private List<RankedRecord> RankedRecords = new() {
new("raidablebases.ladder.easy", "raideasy", RaidableMode.Easy), new("raidablebases.ladder.medium", "raidmedium", RaidableMode.Medium), new("raidablebases.ladder.hard", "raidhard", RaidableMode.Hard), new("raidablebases.ladder.expert", "raidexpert", RaidableMode.Expert), new("raidablebases.ladder.nightmare", "raidnightmare", RaidableMode.Nightmare), new("raidablebases.th", "raidhunter", RaidableMode.Points),
}
;
public enum SphereColor {
None, Blue, Cyan, Green, Magenta, Purple, Red, Yellow
}
public enum RaidableType {
None, Manual, Scheduled, Purchased, Maintained, Grid
}
public enum RaidableMode {
Disabled = -1, Easy = 0, Medium = 1, Hard = 2, Expert = 3, Nightmare = 4, Points = 8888, Random = 9999
}
public enum AlliedType {
All, Clan, Friend, Team
}
public enum CacheType {
Close, Delete, Generic, Temporary, Privilege, Seabed, Submerged
}
public enum ConstructionType {
Barricade, Ladder, Any
}
public class TemporaryData {
public HashSet<ulong> NetworkIdentifiers = new();
internal bool IsLoaded;
public TemporaryData() {
}
}
public class StoredData {
public RotationCycle Cycle = new();
public Dictionary<string, Lockout> Lockouts = new();
public Dictionary<string, PlayerInfo> Players = new();
public Dictionary<ulong, BuyableInfo> BuyableCooldowns = new();
public DateTime RaidTime = DateTime.MinValue;
public int TotalEvents;
public StoredData() {
}
}
public class RandomBase {
public string username;
public ulong userid;
public IPlayer user;
public BasePlayer owner;
public PasteData pasteData;
public string BaseName;
public Vector3 Position;
public BaseProfile Profile;
public RaidableType type;
public RaidableSpawns spawns;
public Payments payments = new();
public float heightAdj;
public float typeDistance;
public float protectionRadius;
public float safeRadius;
public float buildRadius;
public float baseHeight;
public int attempts;
public bool autoHeight;
public bool stability;
public bool checkTerrain;
public bool Sorted;
public RaidableBases Instance;
public RaidableBase raid;
public HashSet<ulong> members = new();
public bool IsTeleportPending(BasePlayer player, Vector3 v) {
return type == RaidableType.Purchased && options.CustomSpawns.BuyableTeleportPositions.Count > 0 && player.HasPermission("raidablebases.buyraid.prefabteleport") && options.CustomSpawns.HasTeleportPositionAt(v);
}
public RandomBase(RaidableBases Instance, string BaseName, BaseProfile Profile, Vector3 Position, RaidableType Type, RaidableSpawns spawns, Payments payments) {
this.Instance = Instance;
this.BaseName = BaseName;
this.Profile = Profile;
this.Position = Position;
this.type = Type;
this.spawns = spawns ??= new(Instance);
this.payments = payments ??= new();
pasteData = Instance.GetPasteData(BaseName);
}
public BuildingOptions options => Profile.Options;
public bool isBuyableEvent => payments.position != Vector3.zero;
public bool hasSpawns => spawns.Spawns.Count > 0 || spawns.Seabed.Count > 0 && options.Water.SpawnOnSeabed;
public void TrySortByDistance() {
if (!Sorted && isBuyableEvent && Instance.config.Settings.Buyable.Closest) {
Instance.Message(owner, "BuyBaseLocate");
var positions = spawns.GetLocations(options.Water.CacheType).ToList();
positions.Sort((x, y) => (x.Location - payments.position).sqrMagnitude.CompareTo((y.Location - payments.position).sqrMagnitude));
if (options.Water.CacheType == CacheType.Seabed) {
spawns.Seabed = new(positions);
}
else spawns.Spawns = new(positions);
Sorted = true;
}
else Sorted = false;
}
}
public class BackpackData {
public DroppedItemContainer backpack;
public BasePlayer _player;
public ulong userID;
public BasePlayer player => _player = ((bool)_player ? _player : RustCore.FindPlayerById(userID));
}
public class BuyableInfo {
public string Easy, Medium, Hard, Expert, Nightmare;
public string Get(RaidableMode mode) => mode switch {
RaidableMode.Easy => Easy, RaidableMode.Medium => Medium, RaidableMode.Hard => Hard, RaidableMode.Expert => Expert, _ => Nightmare
}
;
private static double GetTimeRemaining(RaidableBases m, ulong userid, RaidableMode mode) {
if (m.config.Settings.Buyable.Cooldowns.Get(mode).Cooldown <= 0 || !m.data.BuyableCooldowns.TryGetValue(userid, out var bi) || !DateTime.TryParse(bi.Get(mode), out var date)) return 0;
return Math.Max(0, date.Subtract(DateTime.Now).TotalSeconds);
}
public static double GetTimeRemaining(RaidableBases m, BasePlayer buyer, RaidableMode mode, bool message) {
double time = GetTimeRemaining(m, buyer.userID, mode);
if (time > 0 && message) {
m.Message(buyer, "BuyCooldown", Math.Ceiling(time));
}
return time;
}
public static bool HasTimeRemaining(RaidableBases m, ulong userid) {
return GetRaidableModes().Exists(mode => GetTimeRemaining(m, userid, mode) > 0);
}
}
public class TimeSettings {
public RaidableMode mode;
public Timer Timer;
public float time;
public void Destroy() {
if (Timer != null && !Timer.Destroyed) {
Timer.Callback();
Timer.Destroy();
}
}
}
public class DelaySettings : TimeSettings {
public RaidableBase raid;
public bool AllowPVP;
}
public class MountInfo {
public Vector3 position;
public float radius;
public BaseMountable m;
public bool IsClaimed => !m || m.IsDestroyed || m.transform == null || m.GetMounted() != null || !InRange2D(m.transform.position, position, radius);
}
public class RaidEntity {
public RaidableBase raid;
public BaseEntity entity;
public RaidEntity(RaidableBase raid, BaseEntity entity) {
this.raid = raid;
this.entity = entity;
}
}
public class SkinInfo {
public List<ulong> skins = new(), workshopSkins = new(), importedWorkshopSkins = new(), allSkins = new();
}
public class Lockout {
public Dictionary<RaidableMode, DateTime> Levels = new();
public bool Any() => Levels.Exists(level => level.Value > DateTime.Now);
public double Get(RaidableMode mode) {
if (Levels.TryGetValue(mode, out var level) && level > DateTime.Now) {
return level.Subtract(DateTime.Now).TotalSeconds;
}
Levels.Remove(mode);
return 0;
}
public void Set(RaidableMode mode, double time) {
if (!Levels.ContainsKey(mode)) {
Levels[mode] = DateTime.Now.AddSeconds(time);
}
}
}
public class RankedRecord {
public string Permission;
public string Group;
public RaidableMode Mode;
public RankedRecord(string permission, string group, RaidableMode mode) {
(Permission, Group, Mode) = (permission, group, mode);
}
}
public class RaidableSpawnLocation {
public List<Vector3> Surroundings = new();
public Vector3 Location;
public float WaterHeight;
public float TerrainHeight;
public float SpawnHeight;
public float Radius;
public bool AutoHeight;
public RaidableSpawnLocation(Vector3 location) {
Location = location;
}
}
public class ZoneInfo {
internal string ZoneId;
internal Vector3 Position;
internal Vector3 Size;
internal Vector3 extents;
internal Matrix4x4 m;
internal float Distance;
internal bool IsBlocked;
public ZoneInfo(string zoneID, Vector3 position, object radius, object size, bool isBlocked, float dist) {
(IsBlocked, ZoneId, Position) = (isBlocked, zoneID, position);
dist = Mathf.Max(dist, 100f);
if (radius is float r) {
Distance = r + M_RADIUS + dist;
}
if (size is Vector3 v && v != Vector3.zero) {
Size = v + new Vector3(dist, Position.y + 100f, dist);
extents = Size * 0.5f;
m = Matrix4x4.TRS(Position, default, Vector3.one);
}
}
public bool IsPositionInZone(Vector3 a) {
if (Size != Vector3.zero) {
Vector3 v = m.inverse.MultiplyPoint3x4(a);
return v.x <= extents.x && v.x > -extents.x && v.y <= extents.y && v.y > -extents.y && v.z <= extents.z && v.z > -extents.z;
}
return InRange2D(Position, a, Distance);
}
}
public class BaseProfile {
public List<LootItem> BaseLootList = new();
public BuildingOptions Options = new();
public RaidableSpawns Spawns;
public string ProfileName;
public RaidableBases Instance;
public BaseProfile(RaidableBases instance) {
Instance = instance;
Spawns = new(instance);
Options.AdditionalBases = new();
Options.NPC.SetAccuracy(Options.Mode);
}
public BaseProfile(RaidableBases instance, BuildingOptions options, string name) {
Spawns = new(instance);
Instance = instance;
Options = options;
ProfileName = name;
}
public static BaseProfile Clone(BaseProfile profile) {
return new BaseProfile(profile.Instance) {
BaseLootList = profile.BaseLootList, Options = profile.Options.Clone(), ProfileName = profile.ProfileName, Spawns = profile.Spawns
}
;
}
}
internal class BuildingTables {
public Dictionary<RaidableMode, List<LootItem>> DifficultyLootLists = new();
public Dictionary<DayOfWeek, List<LootItem>> WeekdayLootLists = new();
public Dictionary<string, BaseProfile> Profiles = new();
public List<string> Removed = new();
public bool IsConfigured(string baseName) => Profiles.Exists(m => m.Key == baseName || m.Value.Options.AdditionalBases.ContainsKey(baseName));
public bool TryGetValue(string baseName, out BaseProfile profile) {
profile = Profiles.FirstOrDefault(m => m.Key == baseName || m.Value.Options.AdditionalBases.ContainsKey(baseName)).Value;
return profile != null;
}
public void Remove(string baseName) {
if (Profiles.Remove(baseName) || Profiles.Values.Exists(m => m.Options.AdditionalBases.Remove(baseName))) {
Removed.Add(baseName);
}
}
}
public GridControllerManager GridController = new();
public class GridControllerManager {
internal RaidableBases Instance;
internal Dictionary<RaidableType, RaidableSpawns> Spawns = new();
internal Coroutine gridCoroutine;
internal Coroutine fileCoroutine;
internal float gridTime;
public SpawnsControllerManager SpawnsController => Instance.SpawnsController;
public StoredData data => Instance.data;
public Configuration config => Instance.config;
public double GetRaidTime() => data.RaidTime.Subtract(DateTime.Now).TotalSeconds;
public void StartAutomation() {
if (Instance.Automated.IsScheduledEnabled) {
if (data.RaidTime != DateTime.MinValue && GetRaidTime() > config.Settings.Schedule.IntervalMax) {
data.RaidTime = DateTime.MinValue;
}
Instance.Automated.StartCoroutine(RaidableType.Scheduled);
}
if (Instance.Automated.IsMaintainedEnabled) {
Instance.Automated.StartCoroutine(RaidableType.Maintained);
}
}
private IEnumerator LoadFiles() {
Instance.Buildings = new();
yield return Instance.LoadTables();
yield return Instance.LoadProfiles();
if (Instance.Buildings.Profiles.Count == 0) {
CriticalError();
yield break;
}
Dictionary<string, int> custom = new();
foreach (var prefab in World.Serialization.world.prefabs) {
if (StringPool.toString.TryGetValue(prefab.id, out var fullname)) {
TryAddCustomSpawn(prefab, fullname, new(prefab.position.x, prefab.position.y, prefab.position.z), custom);
}
}
foreach (var (type, amount) in custom) Puts($"Loaded {amount} custom spawns from {type}");
yield return CoroutineEx.waitForSeconds(5f);
Instance.IsSpawnerBusy = false;
StartAutomation();
}
public void Setup() {
if (Spawns.Count >= 5) {
fileCoroutine = ServerMgr.Instance.StartCoroutine(LoadFiles());
return;
}
StopCoroutine();
gridCoroutine = ServerMgr.Instance.StartCoroutine(GenerateGrid());
}
public void StopCoroutine() {
if (gridCoroutine != null) {
ServerMgr.Instance.StopCoroutine(gridCoroutine);
gridCoroutine = null;
}
if (fileCoroutine != null) {
ServerMgr.Instance.StopCoroutine(fileCoroutine);
fileCoroutine = null;
}
}
private void CriticalError(string text = "No valid profiles exist!") {
if (Instance.profileErrors.Count > 0) {
Puts("Json errors found in:");
Instance.profileErrors.ForEach(str => Puts(str));
}
Puts("ERROR: Grid has failed initialization.{0}", text);
Interface.Oxide.NextTick(() => gridCoroutine = null);
}
public bool BadFrameRate;
private IEnumerator GenerateGrid() // Credits to Jake_Rich for helping me with this!
{
yield return CoroutineEx.waitForSeconds(0.1f);
while (Performance.report.frameRate < 15 && ConVar.FPS.limit > 15) {
BadFrameRate = true;
yield return CoroutineEx.waitForSeconds(1f);
}
BadFrameRate = false;
Stopwatch gridStopwatch = Stopwatch.StartNew();
RaidableSpawns spawns = Spawns[RaidableType.Grid] = new(Instance);
gridTime = Time.realtimeSinceStartup;
Instance.Buildings = new();
yield return Instance.LoadTables();
yield return Instance.LoadProfiles();
yield return SpawnsController.SetupMonuments();
if (Instance.Buildings.Profiles.Count == 0) {
gridStopwatch.Stop();
CriticalError();
yield break;
}
var spawnOnSeabed = false;
var minPos = (int)(World.Size / -2f);
var maxPos = (int)(World.Size / 2f);
var maxProtectionRadius = -10000f;
var minProtectionRadius = 10000f;
var maxWaterDepthSeabed = 0f;
var minWaterDepthSeabed = 0f;
var maxWaterDepth = 0f;
var landLevel = 0.5f;
var checks = 0;
foreach (var profile in Instance.Buildings.Profiles.Values) {
if (profile.Options.Water.Seabed > 0f) spawnOnSeabed = true;
maxProtectionRadius = Mathf.Max(profile.Options.ProtectionRadii.Max(), maxProtectionRadius);
minProtectionRadius = Mathf.Min(profile.Options.ProtectionRadii.Min(), minProtectionRadius);
maxWaterDepthSeabed = Mathf.Min(maxWaterDepthSeabed, profile.Options.Water.MaximumSeabedWaterDepth);
minWaterDepthSeabed = Mathf.Min(minWaterDepthSeabed, profile.Options.Water.MinimumSeabedWaterDepth);
maxWaterDepth = Mathf.Max(maxWaterDepth, profile.Options.Water.WaterDepth);
landLevel = Mathf.Max(Mathf.Clamp(profile.Options.LandLevel, 0.5f, 3f), landLevel);
}
if (!config.Settings.Management.AllowOnBeach && !config.Settings.Management.AllowInland && !spawnOnSeabed) {
gridStopwatch.Stop();
CriticalError("Spawn options for beach, inland and seabed are disabled!");
yield break;
}
var blockedPositions = config.Settings.Management.BlockedPositions.ToList();
var zero = blockedPositions.Find(x => x.position == Vector3.zero);
if (zero == null) {
blockedPositions.Add(zero = new(Vector3.zero, 200f));
}
if (zero.radius < 200f) {
zero.radius = 200f;
}
var wtObj = Interface.Oxide.CallHook("GetGridWaitTime");
var waitTime = CoroutineEx.waitForSeconds(wtObj is float w ? w : 0.0035f);
var threshold = Interface.Oxide.CallHook("GetGridWaitThreshold") is int th ? th : 25;
var prefabs = config.Settings.Management.BlockedPrefabs.ToDictionary(pair => pair.Key, pair => pair.Value);
var blockedMapPrefabs = Pool.Get<Dictionary<Vector3, float>>();
var custom = new Dictionary<string, int>();
prefabs.Remove("test_prefab");
prefabs.Remove("test_prefab_2");
foreach (var prefab in World.Serialization.world.prefabs) {
if (!StringPool.toString.TryGetValue(prefab.id, out var fullname)) {
continue;
}
Vector3 v = new(prefab.position.x, prefab.position.y, prefab.position.z);
if (prefabs.Count > 0 && prefabs.TryGetValue(GetFileNameWithoutExtension(fullname), out var dist)) {
blockedMapPrefabs[v] = dist;
}
TryAddCustomSpawn(prefab, fullname, v, custom);
}
bool hasBlockedMapPrefabs = blockedMapPrefabs.Count > 0;
bool hasBlockedPositions = blockedPositions.Count > 0;
double totalPoints = Math.Pow((maxPos - minPos) / CELL_SIZE, 2);
double step = totalPoints / 4.0;
int stepCounter = 0;
int progress = 0;
for (float x = minPos; x < maxPos; x += CELL_SIZE) {
for (float z = minPos; z < maxPos; z += CELL_SIZE) {
progress++;
if (++stepCounter >= step) {
Puts($"{Math.Round((progress / totalPoints) * 100.0)}% loaded ({spawns.Spawns.Count + spawns.Seabed.Count} potential points)");
stepCounter = 0;
}
var position = new Vector3(x, 0f, z);
if (hasBlockedPositions && blockedPositions.Exists(a => InRange2D(position, a.position, a.radius))) {
continue;
}
position.y = SpawnsController.GetSpawnHeight(position);
if (hasBlockedMapPrefabs && SpawnsController.IsBlockedByMapPrefab(blockedMapPrefabs, position)) {
continue;
}
SpawnsController.ExtractLocation(spawns, position, landLevel, minProtectionRadius, maxProtectionRadius, minWaterDepthSeabed, maxWaterDepthSeabed, maxWaterDepth, spawnOnSeabed);
if (++checks >= threshold) {
checks = 0;
yield return waitTime;
}
}
}
blockedMapPrefabs.ResetToPool();
Instance.IsSpawnerBusy = false;
Instance.GridController.StartAutomation();
Instance.Queues.Messages.Clear();
gridStopwatch.Stop();
Puts(Instance.mx("Initialized Grid", null, Math.Floor(gridStopwatch.Elapsed.TotalSeconds), gridStopwatch.Elapsed.Milliseconds, World.Size, spawns.Spawns.Count));
if (spawns.Seabed.Count > 0) Puts(Instance.mx("Initialized Grid Sea", null, spawns.Seabed.Count));
foreach (var (type, amount) in custom) Puts($"Loaded {amount} custom spawns from {type}");
gridCoroutine = null;
}
public void TryAddCustomSpawn(ProtoBuf.PrefabData prefab, string fullname, Vector3 v, Dictionary<string, int> custom) {
foreach (var (name, profile) in Instance.Buildings.Profiles) {
if (profile.Options.CustomSpawns.ShouldAdd(profile, prefab, fullname, v)) {
if (custom.ContainsKey(name)) {
custom[name]++;
}
else custom[name] = 1;
}
}
}
public void LoadSpawns() {
Spawns = new();
Spawns.Add(RaidableType.Grid, new(Instance));
LoadSpawnsForType(RaidableType.Manual, config.Settings.Manual.SpawnsFile, "LoadedManual");
LoadSpawnsForType(RaidableType.Scheduled, config.Settings.Schedule.SpawnsFile, "LoadedScheduled");
LoadSpawnsForType(RaidableType.Maintained, config.Settings.Maintained.SpawnsFile, "LoadedMaintained");
LoadSpawnsForType(RaidableType.Purchased, config.Settings.Buyable.SpawnsFile, "LoadedBuyable");
}
private void LoadSpawnsForType(RaidableType type, string spawnsFile, string key) {
if (SpawnsFileValid(spawnsFile)) {
var spawns = GetSpawnsLocations(spawnsFile);
if (spawns.Count > 0) {
Puts(Instance.mx(key, null, spawns.Count));
Spawns[type] = new(Instance, spawns);
}
}
}
public bool SpawnsFileValid(string spawnsFile) {
if (string.IsNullOrEmpty(spawnsFile) || spawnsFile.Equals("none", StringComparison.OrdinalIgnoreCase)) {
return false;
}
return Instance.FileExists($"SpawnsDatabase{Path.DirectorySeparatorChar}{spawnsFile}");
}
public HashSet<RaidableSpawnLocation> GetSpawnsLocations(string spawnsFile) {
try {
return new(Interface.Oxide.DataFileSystem.ReadObject<Spawnfile>($"SpawnsDatabase{Path.DirectorySeparatorChar}{spawnsFile}").spawnPoints.Values.Select(value => new RaidableSpawnLocation(value.ToString().ToVector3())));
}
catch {
Puts("Invalid spawns file: {0}", spawnsFile);
return new();
}
}
}
private class Spawnfile {
public Dictionary<string, object> spawnPoints = new();
}
public class QueueController {
internal YieldInstruction instruction0;
internal YieldInstruction instruction1;
internal Queue<RandomBase> queue = new();
internal DebugMessages Messages = new();
internal Coroutine _coroutine;
internal int spawnChecks;
internal bool Paused;
internal RaidableBases Instance;
internal Configuration config => Instance.config;
internal SpawnsControllerManager SpawnsController => Instance.SpawnsController;
internal bool Any => queue.Count > 0;
private void Message(BasePlayer player, string key, params object[] args) => Instance.Message(player, key, args);
private string mx(string key, string id = null, params object[] args) => Instance.mx(key, id, args);
public class DebugMessages {
internal Dictionary<string, Info> _elements = new();
internal RaidableBases _instance;
internal bool _logToFile;
internal IPlayer _user;
public class Info {
public int Amount = 1;
public List<string> Values = new();
public override string ToString() => Values.Count > 0 ? $": {string.Join(", ", Values)}" : string.Empty;
}
public string Add(string element, object obj = null) {
if (string.IsNullOrEmpty(element)) {
return null;
}
if (!_elements.TryGetValue(element, out var info)) {
if (_elements.Count >= 20) {
_elements.Remove(_elements.ElementAt(0).Key);
}
_elements[element] = info = new();
}
else info.Amount++;
if (obj == null) {
return element;
}
string value = obj.ToString().Replace("(", "").Replace(")", "").Replace(",", "");
if (!info.Values.Contains(value)) {
if (info.Values.Count >= 5) {
info.Values.RemoveAt(0);
}
info.Values.Add(value);
}
return $"{element}: {value}";
}
public void Clear() {
_elements.Clear();
}
public bool Any() {
return _elements.Count > 0;
}
public void PrintAll(IPlayer user = null) {
if (_elements.Count > 0 && _instance.DebugMode) {
foreach (var (key, info) in _elements) {
PrintInternal(user, $"{info.Amount}x - {key}{info.ToString()}");
}
Clear();
}
}
private bool PrintInternal(IPlayer user, string message) {
if (!string.IsNullOrEmpty(message) && _instance.DebugMode) {
if (_logToFile) {
_instance.LogToFile("debug", message, _instance, true);
}
if (user == null) {
Puts("DEBUG: {0}", message);
}
else user.Reply($"DEBUG: {message}");
return true;
}
return false;
}
public void Log(string baseName, string message) {
_instance?.Buildings?.Remove(baseName);
_instance.IsSpawnerBusy = false;
Print(message);
Puts(message);
}
public bool Print(string message) {
Print(_user, message, null);
return false;
}
public void Print(string message, object obj) {
Print(_user, message, obj);
}
public void Print(IPlayer user, string message, object obj) {
if (!PrintInternal(user, obj == null ? message : $"{message}: {obj}")) {
Add(message, obj);
}
}
public void PrintLast(string id = null) {
if (_elements.Count > 0 && _instance.DebugMode) {
PrintInternal(_user, GetLast(id));
}
}
public string GetLast(string id = null) {
if (_elements.Count == 0) {
return _instance.m("CannotFindPosition", id);
}
var (key, info) = _elements.ElementAt(_elements.Count - 1);
_elements.Remove(key);
return $"{info.Amount}x - {key}{info.ToString()}";
}
}
public QueueController(RaidableBases instance) {
Messages._instance = Instance = instance;
Messages._logToFile = instance.config.LogToFile;
spawnChecks = Mathf.Clamp(instance.config.Settings.Management.SpawnChecks, 1, 500);
instruction0 = CoroutineEx.waitForSeconds(0.025f);
instruction1 = CoroutineEx.waitForSeconds(1f);
}
public void StartCoroutine() {
StopCoroutine();
_coroutine = ServerMgr.Instance.StartCoroutine(FindEventPosition());
}
public void StopCoroutine() {
if (_coroutine != null) {
ServerMgr.Instance.StopCoroutine(_coroutine);
_coroutine = null;
}
queue.ToList().ForEach(rb => rb.payments.Refund());
queue.Clear();
}
public void Add(RandomBase sp) {
if (!queue.Contains(sp)) {
queue.Enqueue(sp);
}
}
private void Spawn(RandomBase rb, Vector3 position) {
if (!Instance.IsUnloading) {
rb.Position = position;
if (Instance.PasteBuilding(rb)) {
Instance.IsSpawnerBusy = true;
CompletePurchase(rb);
Teleport(rb);
}
}
}
private void CompletePurchase(RandomBase rb) {
if (rb.type == RaidableType.Purchased) {
if (!rb.owner.IsKilled()) {
var grid = Instance.FormatGridReference(rb.owner, rb.Position);
if (rb.owner.HasPermission("raidablebases.despawn.buyraid")) {
if (config.Settings.Buyable.Refunds.Enabled) {
Message(rb.owner, "BuyRefundableBaseSpawnedAt", rb.Position, grid, config.Settings.EventCommand, config.Settings.Buyable.Refunds.Percentage);
}
else Message(rb.owner, "BuyCancellationsBaseSpawnedAt", rb.Position, grid, config.Settings.EventCommand);
}
else Message(rb.owner, "BuyBaseSpawnedAt", rb.Position, grid);
if (config.EventMessages.AnnounceBuy) {
foreach (var target in BasePlayer.activePlayerList) {
Message(target, "BuyBaseAnnouncement", rb.owner.displayName, rb.Position, Instance.FormatGridReference(target, rb.Position));
}
}
}
Puts(mx("BuyBaseAnnouncementConsole", null, rb.username, rb.options.Mode, rb.BaseName, rb.Position, Instance.PositionToGrid(rb.Position)));
config.Settings.Buyable.Cooldowns.Set(Instance, rb.members, rb.userid, rb.options.Mode, true);
}
}
private void Teleport(RandomBase rb) {
if (rb.user != null && rb.user.IsAdmin && rb.user.IsConnected && !rb.IsTeleportPending(rb.user.Player(), rb.Position)) {
rb.user.Teleport(rb.Position.x, rb.Position.y, rb.Position.z);
}
}
private bool CanBypassPause(RandomBase rb) {
if (rb.type == RaidableType.Purchased) {
return rb.userid.HasPermission("raidablebases.canbypass");
}
if (rb.type == RaidableType.Manual) {
return rb.user != null;
}
return false;
}
private IEnumerator FindEventPosition() {
int checks = 0;
while (!Instance.IsUnloading) {
if (++checks >= spawnChecks) {
yield return instruction0;
checks = 0;
}
if (!queue.TryPeek(out var spq)) {
yield return instruction1;
continue;
}
if (Instance.Buildings.Removed.Contains(spq.BaseName)) {
if (spq.type == RaidableType.Scheduled) {
Instance.data.RaidTime = DateTime.Now.AddSeconds(1f);
}
queue.Dequeue();
continue;
}
if (spq.Position != Vector3.zero) {
Spawn(spq, spq.Position);
queue.Dequeue();
yield return instruction1;
continue;
}
if (!spq.isBuyableEvent && queue.Exists(sp => sp.isBuyableEvent)) {
queue.Enqueue(queue.Dequeue());
continue;
}
spq.TrySortByDistance();
spq.spawns.Check();
while (spq.hasSpawns) {
if (++checks >= spawnChecks) {
checks = 0;
yield return instruction0;
}
if (Instance.IsSpawnerBusy || Paused && !CanBypassPause(spq)) {
yield return instruction1;
continue;
}
spq.attempts++;
var rsl = spq.spawns.GetRandom(spq.options.Water, spq.isBuyableEvent);
var v = rsl.Location;
if (!TopologyChecks(spq, v)) {
continue;
}
v.y = GetAdjustedHeight(spq, v);
if (IsTooClose(spq, v)) {
continue;
}
if (IsAreaManuallyBlocked(spq, v)) {
continue;
}
if (CanSpawnCustom(spq, spq.type, v, spq.options.CustomSpawns.Ignore, spq.options.CustomSpawns.SafeRadius)) {
yield return instruction1;
break;
}
if (CanSpawnCustom(spq, RaidableType.Maintained, v, config.Settings.Maintained.Ignore, config.Settings.Maintained.SafeRadius)) {
yield return instruction1;
break;
}
if (CanSpawnCustom(spq, RaidableType.Scheduled, v, config.Settings.Schedule.Ignore, config.Settings.Schedule.SafeRadius)) {
yield return instruction1;
break;
}
if (CanSpawnCustom(spq, RaidableType.Purchased, v, config.Settings.Buyable.Ignore, config.Settings.Buyable.SafeRadius)) {
yield return instruction1;
break;
}
if (IsSubmerged(spq, rsl, v)) {
continue;
}
if (!IsAreaSafe(spq, rsl, v)) {
continue;
}
if (!spq.pasteData.valid) {
yield return SetupCopyPasteRadius(spq);
}
if (spq.pasteData.foundations.IsNullOrEmpty()) {
Instance.Buildings.Remove(spq.BaseName);
break;
}
if (IsObstructed(spq, v)) {
continue;
}
Spawn(spq, v);
yield return instruction1;
break;
}
if (Instance.IsGridLoading() && !Instance.IsSpawnerBusy) {
yield return instruction1;
continue;
}
queue.Dequeue();
CheckSpawner(spq);
}
_coroutine = null;
}
private void CheckSpawner(RandomBase spq) {
if (!Instance.IsSpawnerBusy) {
if (spq.type == RaidableType.Manual) {
if (spq.user == null || spq.user.IsServer) {
Puts(mx("CannotFindPosition"));
}
else {
Message(spq.user.Player(), Instance.Queues.Messages.GetLast(spq.user.Id));
}
}
else if (spq.isBuyableEvent) {
spq.payments.Refund();
Message(spq.owner, Instance.mx("CannotFindPosition", spq.userid.ToString()));
}
spq.spawns.TryAddRange();
Messages.PrintAll();
Instance.data.Cycle.Add(spq.type, spq.options.Mode, spq.BaseName);
}
}
private bool IsObstructed(RandomBase spq, Vector3 v) {
if (!spq.spawns.IsCustomSpawn && SpawnsController.IsObstructed(v, spq.pasteData.radius, Mathf.Clamp(spq.options.LandLevel, -3f, 3f), spq.options.Setup.ForcedHeight, spq.options.Water.SpawnOnSeabed)) {
Messages.Add("Area is obstructed", v);
spq.spawns.RemoveNear(v, spq.protectionRadius / 2f, spq.options.Water.SpawnOnSeabed ? CacheType.Seabed : CacheType.Temporary, spq.type);
return true;
}
return false;
}
private IEnumerator SetupCopyPasteRadius(RandomBase spq) {
yield return Instance.SetupCopyPasteObstructionRadius(spq.BaseName, spq.options.ProtectionRadii.Obstruction == -1 ? 0f : GetObstructionRadius(spq.options.ProtectionRadii, RaidableType.None));
}
private bool IsAreaSafe(RandomBase spq, RaidableSpawnLocation rsl, Vector3 v) {
if (!SpawnsController.IsAreaSafe(v, spq.safeRadius, spq.buildRadius, queueLayers, out var cacheType, spq.type, spq.options.CustomSpawns)) {
if (spq.options.Water.SpawnOnSeabed) {
cacheType = CacheType.Seabed;
}
if (cacheType == CacheType.Delete) {
spq.spawns.Remove(rsl, cacheType);
}
else if (cacheType == CacheType.Privilege) {
spq.spawns.RemoveNear(v, spq.buildRadius, cacheType, spq.type);
}
else spq.spawns.RemoveNear(v, spq.safeRadius / 2f, cacheType, spq.type);
return false;
}
return true;
}
// ... [Code preview truncated due to length. Full source in attached file] ...