C#:
/*
* < ----- End-User License Agreement ----->
*
* You may not merge, publish, distribute, sublicense, or sell copies of This Software without the Developer�s consent.Copy or modify is allowed for personal use only.*
* 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 � nivex.All rights reserved.---- -- --- --- --------- --- ---
-- -- -- -------- ----- - -------
--- -- ------- --- -------- ----
---- ��------- --- ------ - -------
---- -------- ---- ----------- ---
- -- - - -- - �- -- -- --- - -
- -- - -- - - - -- - - - - - - -
- - - - - -- - - - -
- - - - - - -
*/
using Facepunch;
using Facepunch.Extend;
using Network;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
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 Rust;
using System;
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics;
using System.Globalization;
using System.IO;
//REM:using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.Serialization;
using System.Text;
using System.Text.RegularExpressions;
using UnityEngine;
using UnityEngine.AI;
using UnityEngine.SceneManagement;
using static Oxide.Plugins.RaidableBasesExtensionMethods.ExtensionMethods;
namespace Oxide.Plugins {
[Info("Raidable Bases", "nivex", "3.0.1")]
[Description("Create fully automated raidable bases with npcs.")]
public class RaidableBases : RustPlugin {
[PluginReference]
Plugin AbandonedBases, DangerousTreasures, ZoneManager, BankSystem, IQEconomic, Economics, ServerRewards, GUIAnnouncements, AdvancedAlerts, Archery, Space, Sputnik, PocketDimensions, FauxAdmin, IQDronePatrol, Friends, Clans, Kits, TruePVE, SimplePVE, NightLantern, Wizardry, NextGenPVE, Imperium, Backpacks, BaseRepair, Notify, SkillTree, ShoppyStock, BuyableBases, XPerience, XLevels;
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 = 2294528;
private const int gridLayers = 327936;
private const float M_RADIUS = 25f;
private const float CELL_SIZE = 12.5f;
private float OceanLevel;
private bool wiped;
private bool buyableEnabled;
private bool IsUnloading;
private bool IsShuttingDown;
private bool bypassRestarting;
private bool DebugMode;
private int despawnLimit = 10;
//REM:private MethodInfo MountWeaponMethod;
private SkinSettingsImportedWorkshop ImportedWorkshopSkins = new();
private ProtectionProperties _elevatorProtection;
private ProtectionProperties _turretProtection;
private AutomatedController Automated;
private StoredData data = new();
public BuildingTables Buildings = new();
public QueueController Queues;
private SkinsPlugin skinsPlugin = new();
private Coroutine checkPlayersNearEventsCo;
private Coroutine setupCopyPasteObstructionRadius;
private List<Coroutine> loadCoroutines = new();
public List<string> RaidableModes = new();
public List<RaidableBase> Raids = new();
public List<(ulong userid, DelaySettings? ds)> PvpDelay = new();
public Dictionary<string, SkinInfo> Skins = new();
private Dictionary<string, PasteData> _pasteData = new();
private Dictionary<ulong, HumanoidBrain> HumanoidBrains = new();
private Dictionary<NetworkableId, BMGELEVATOR> _elevators = new();
private Dictionary<string, ItemDefinition> DeployableItems = new();
private Dictionary<ItemDefinition, string> ItemDefinitions = new();
private Dictionary<string, ItemDefinition> _itemModEntity = new();
private Dictionary<ItemDefinition, ItemModConsume> _itemModConsume = new();
private readonly List<string> ExcludedMounts = new() {
"beachchair", "boogieboard", "cardtable", "chair", "chippyarcademachine", "computerstation", "drumkit", "microphonestand", "piano", "secretlabchair", "slotmachine", "sofa", "xylophone"
}
;
private readonly List<string> Blocks = new() {
"wall.frame.cell", "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 readonly 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 readonly List<string> arguments = new() {
"add", "remove", "list", "clean", "easy", "med", "medium", "hard", "expert", "nightmare", "0", "1", "2", "3", "4", "toggle", "stability", "maintained", "scheduled", "noexplosivecosts"
}
;
private readonly List<uint> CupboardPrefabIDs = new() {
2476970476, 785685130, 3932172323
}
;
private readonly IPlayer _consolePlayer = new Game.Rust.Libraries.Covalence.RustConsolePlayer();
private readonly 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() {
}
}
public static class RaidableMode {
public const string Easy = en ? "Easy" : "������";
public const string Medium = en ? "Medium" : "�������";
public const string Hard = en ? "Hard" : "������";
public const string Expert = en ? "Expert" : "�������";
public const string Nightmare = en ? "Nightmare" : "���������";
public const string Legacy = "Legacy", Random = "Random", Points = "Points", Disabled = "Disabled";
}
public struct DamageMultiplier {
public DamageType index;
public float amount;
}
public enum DamageResult {
None, Allowed, Blocked
}
public enum SphereColor {
None, Blue, Cyan, Green, Magenta, Purple, Red, Yellow
}
public enum RaidableType {
None, Manual, Scheduled, Purchased, Maintained, Grid
}
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 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 int protocol = -1;
public StoredData() {
}
}
public class RandomBase {
public float heightAdj, typeDistance, protectionRadius, safeRadius, ignoreRadius, buildRadius, baseHeight;
public bool autoHeight, stability, checkTerrain, Sorted, Save;
public string BaseName, username, id;
public int attempts, errors;
public ulong userid;
public Vector3 Position;
public IPlayer user;
public RaidableType type;
public BasePlayer owner;
public PasteData pasteData;
public BaseProfile Profile;
public RaidableSpawns spawns;
public RaidableBases Instance;
public RaidableBase raid;
public Payments payments = new();
public HashSet<ulong> members = new();
public BuildingOptions options => Profile.Options;
public bool isBuyableEvent => payments.position != Vector3.zero;
public bool HasSpawns() => options.Water.Seabed >= 100f ? spawns.Seabed.Count > 0 : spawns.Spawns.Count > 0 || spawns.Seabed.Count > 0;
public void TrySortByDistance() {
if (!Sorted && isBuyableEvent && Instance.config.Settings.Buyable.Closest) {
Instance.Message(owner, "BuyBaseLocate");
using var tmp = spawns.GetLocations(options.Water.CacheType).ToPooledList();
tmp.Sort((x, y) => (x.Location - payments.position).sqrMagnitude.CompareTo((y.Location - payments.position).sqrMagnitude));
var set = options.Water.CacheType == CacheType.Seabed ? spawns.Seabed : spawns.Spawns;
set.Clear();
set.UnionWith(tmp);
Sorted = true;
}
else Sorted = false;
}
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 class BackpackData : Pool.IPooled {
public BackpackData() {
}
public void EnterPool() {
Pool.FreeUnmanaged(ref containers);
_player = null;
userid = 0uL;
}
public void LeavePool() => containers = Pool.Get<List<DroppedItemContainer>>();
public List<DroppedItemContainer> containers;
public BasePlayer _player;
public ulong userid;
public bool IsEmpty => containers.Count == 0 || containers.All(x => x.IsKilled());
public BasePlayer player {
get {
if (_player == null) {
_player = RustCore.FindPlayerById(userid);
}
return _player;
}
}
}
public class BuyableInfo {
public Dictionary<string, DateTime> Modes = new(StringComparer.OrdinalIgnoreCase);
public static double GetTimeRemaining(RaidableBases m, ulong userid, string mode) {
if (m.config.Settings.Buyable.Cooldowns.Get(mode).Cooldown <= 0 || !m.data.BuyableCooldowns.TryGetValue(userid, out var info) || !info.Modes.TryGetValue(mode, out DateTime date)) {
return 0;
}
return Math.Max(0, date.Subtract(DateTime.Now).TotalSeconds);
}
public static double GetTimeRemaining(RaidableBases m, BasePlayer buyer, string mode, bool message) {
double time = GetTimeRemaining(m, buyer.userID, mode);
if (time > 0 && message) {
m.Message(buyer, "BuyCooldown", m.FormatTime(time, buyer.UserIDString));
}
return time;
}
public static bool HasTimeRemaining(RaidableBases m, ulong userid) {
return m.GetRaidableModes().Exists(mode => GetTimeRemaining(m, userid, mode) > 0);
}
}
public class TimeSettings {
public string 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 class SkinInfo {
public List<ulong> skins = new(), workshopSkins = new(), importedWorkshopSkins = new(), allSkins = new();
}
public class Lockout {
public Dictionary<string, DateTime> Levels = new(StringComparer.OrdinalIgnoreCase);
public bool Any() {
foreach (var level in Levels) {
if (level.Value > DateTime.Now) {
return true;
}
}
return false;
}
public double Get(string 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(string mode, double time) {
if (!Levels.ContainsKey(mode)) {
Levels[mode] = DateTime.Now.AddSeconds(time);
}
}
}
public class RankedRecord {
public string Permission = string.Empty;
public string Group = string.Empty;
public string Mode = string.Empty;
internal bool IsValid => !string.IsNullOrWhiteSpace(Permission) && !string.IsNullOrWhiteSpace(Group) && !string.IsNullOrWhiteSpace(Mode);
public RankedRecord(string permission, string group, string mode) {
(Permission, Group, Mode) = (permission, group, mode);
}
public RankedRecord() {
}
}
public class RaidableSpawnLocation : IEquatable<RaidableSpawnLocation> {
public List<Vector3> Surroundings = new();
public Vector3 Location;
public MinMax LandLevel;
public float WaterHeight;
public float TerrainHeight;
public float SpawnHeight;
public float Radius;
public float RailRadius;
public bool AutoHeight;
public RaidableSpawnLocation(Vector3 location) {
Location = location;
}
public bool Equals(RaidableSpawnLocation other) => Location.Equals(other.Location);
public override bool Equals(object obj) => obj is RaidableSpawnLocation other && Equals(other);
public override int GetHashCode() => base.GetHashCode();
}
public class ZoneInfo {
internal string ZoneId;
internal Vector3 Position;
internal Vector3 Size;
internal Vector3 extents;
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;
}
}
public bool IsPositionInZone(Vector3 a) {
if (Size != Vector3.zero) {
Vector3 v = Quaternion.Inverse(Quaternion.identity) * (a - Position);
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;
private Dictionary<string, BaseProfile> Clones = new();
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, string name) {
if (profile.Clones.TryGetValue(name, out var clone)) {
return clone;
}
profile.Clones[name] = clone = new(profile.Instance) {
BaseLootList = profile.BaseLootList, Options = profile.Options.Clone(), ProfileName = name, Spawns = profile.Spawns
}
;
return clone;
}
}
public class BuildingTables {
public Dictionary<string, List<LootItem>> DifficultyLootLists = new(StringComparer.OrdinalIgnoreCase);
public Dictionary<DayOfWeek, List<LootItem>> WeekdayLootLists = new();
public Dictionary<string, BaseProfile> Profiles = new(StringComparer.OrdinalIgnoreCase);
public List<string> Removed = new();
public bool IsConfigured(string baseName) {
foreach (var m in Profiles) {
if (m.Key == baseName || m.Value.Options.AdditionalBases.ContainsKey(baseName)) {
return true;
}
}
return false;
}
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();
using var sb = DisposableBuilder.Get();
yield return Instance.LoadProfiles(sb);
yield return Instance.LoadTables(sb);
if (Instance.Buildings.Profiles.Count == 0) {
CriticalError();
yield break;
}
using var custom = DisposableList<(string, int)>();
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}");
Instance.ProcessExtensions(ExtOp.Init);
Instance.ProcessExtensions(ExtOp.Validate);
yield return CoroutineEx.waitForSeconds(5f);
Instance.IsSpawnerBusy = false;
StartAutomation();
if (!Instance.IsCopyPasteLoaded(out var error)) Puts(error);
}
public void SetupGrid() {
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() {
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();
using var sb = DisposableBuilder.Get();
yield return Instance.LoadProfiles(sb);
yield return Instance.LoadTables(sb);
yield return SpawnsController.SetupMonuments();
Instance.ProcessExtensions(ExtOp.Init);
Instance.ProcessExtensions(ExtOp.Validate);
if (Instance.Buildings.Profiles.Count == 0) {
gridStopwatch.Stop();
CriticalError();
yield break;
}
var spawnOnSeabed = false;
var minPos = (int)(World.Size / -2f) + 100;
var maxPos = (int)(World.Size / 2f) - 100;
var maxProtectionRadius = -10000f;
var minProtectionRadius = 10000f;
var maxWaterDepthSeabed = 0f;
var minWaterDepthSeabed = 0f;
var maxAutoRadius = 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;
maxAutoRadius = Mathf.Min(profile.Options.ProtectionRadii.Auto(), maxAutoRadius);
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;
}
using var blockedPositions = config.Settings.Management.BlockedPositions.ToPooledList();
using var blockedMapPrefabs = DisposableList<(Vector3, float)>();
using var custom = DisposableList<(string, int)>();
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);
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.Add((v, dist));
}
TryAddCustomSpawn(prefab, fullname, v, custom);
}
float railRadius = Mathf.Max(M_RADIUS * 2f, maxAutoRadius);
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) // Credits to Jake_Rich for helping me with this!
{
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, railRadius, minWaterDepthSeabed, maxWaterDepthSeabed, maxWaterDepth, spawnOnSeabed);
if (++checks >= threshold) {
checks = 0;
yield return waitTime;
}
}
}
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}");
if (!Instance.IsCopyPasteLoaded(out var error)) Puts(error);
gridCoroutine = null;
}
public void TryAddCustomSpawn(ProtoBuf.PrefabData prefab, string fullname, Vector3 v, List<(string type, int amount)> custom) {
foreach (var (type, profile) in Instance.Buildings.Profiles) {
if (profile.Options.CustomSpawns.ShouldAdd(profile, prefab, fullname, v)) {
var index = custom.FindIndex(x => x.type == type);
if (index != -1) {
custom[index] = (custom[index].type, custom[index].amount + 1);
}
else {
custom.Add((type, 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");
}
public bool BlockAtSpawnsDatabase(Vector3 a) {
if (config.Settings.Management.BlockAtSpawnsDatabase) {
foreach (var (type, rs) in Spawns) {
if (rs.IsCustomSpawn && rs.Spawns.Count > 0) {
foreach (var rsl in rs.Spawns) {
if (rsl.Location.Distance(a) <= rsl.Radius) {
return true;
}
}
}
}
}
return false;
}
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.IsNullOrWhiteSpace(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, 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.IsNullOrWhiteSpace(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}");
}
Clear();
}
}
private bool PrintInternal(IPlayer user, string message) {
if (!string.IsNullOrWhiteSpace(message) && _instance.DebugMode) {
if (_logToFile) {
_instance.LogToFile("debug", message, _instance, true);
}
if (user == null || user.IsServer) {
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}";
}
}
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.1f);
instruction1 = CoroutineEx.waitForSeconds(1f);
}
public void RestartCoroutine() {
StopCoroutine();
_coroutine = ServerMgr.Instance.StartCoroutine(FindEventPosition());
}
public void StopCoroutine() {
if (_coroutine != null) {
ServerMgr.Instance.StopCoroutine(_coroutine);
_coroutine = null;
}
queue.ForEach(rb => rb.payments.Refund());
queue.Clear();
}
public void Add(RandomBase rb) {
if (!queue.Contains(rb)) {
queue.Enqueue(rb);
}
}
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, false);
}
}
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);
}
if (spq.type == RaidableType.Purchased) {
spq.payments.Refund();
}
queue.Dequeue();
continue;
}
if (spq.Position != Vector3.zero) {
queue.Dequeue();
Spawn(spq, spq.Position);
yield return instruction1;
continue;
}
if (!spq.isBuyableEvent && HasBuyableEvent()) {
queue.Enqueue(queue.Dequeue());
continue;
}
if (Instance.ZoneManager != null) {
SpawnsController.SetupZones(Instance.ZoneManager, false);
}
bool spawnOnSeabed = spq.options.Water.Random;
spq.TrySortByDistance();
spq.spawns.Check();
spq.options.Water.IsWaterSpawn = spawnOnSeabed;
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, spawnOnSeabed, spq.isBuyableEvent, spq.options.GetLandLevel);
if (rsl == null) {
continue;
}
var v = rsl.Location;
if (!TopologyChecks(spq, v, rsl.RailRadius)) {
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) || SpawnsController.IsZoneBlocked(v)) {
continue;
}
Spawn(spq,
// ... [Code preview truncated due to length. Full source in attached file] ...