C#:
/*
* < ----- 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-2022 nivex
*/
#pragma warning disable
using Facepunch;
using Facepunch.Math;
using Network;
using Newtonsoft.Json;
using Newtonsoft.Json.Serialization;
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.Game.Rust.Libraries;
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.7.3")]
[Description("Create fully automated raidable bases with npcs.")]
internal class RaidableBases : RustPlugin {
[PluginReference]
private Plugin DangerousTreasures, ZoneManager, IQEconomic, Economics, ServerRewards, GUIAnnouncements, AdvancedAlerts, Archery, Space, Sputnik, Friends, Clans, Kits, TruePVE, NightLantern, Wizardry, NextGenPVE, Imperium, Backpacks, BaseRepair, Notify, SkillTree;
private new const string Name = "RaidableBases";
private const int targetMask = Layers.Mask.World | Layers.Mask.Terrain | Layers.Mask.Default;
private const int visibleMask = Layers.Mask.Deployed | Layers.Mask.Construction | targetMask;
private const int targetMask2 = Layers.Mask.Construction | Layers.Mask.Water | targetMask;
private const int manualMask = Layers.Mask.Deployed | Layers.Mask.Tree | targetMask2;
private const int blockLayers = Layers.Mask.Construction | Layers.Mask.Deployed | Layers.Mask.Player_Server;
private const int queueLayers = blockLayers | Layers.Mask.Ragdoll;
private bool UndoStructures, UndoDeployables, UndoMounts, LogSpawnToFile;
private static RaidableBases Instance {
get;
set;
}
private AutomatedController Automated {
get;
set;
}
public QueueController Queues {
get;
set;
}
private Coroutine setupCopyPasteObstructionRadius {
get;
set;
}
private float OceanLevel {
get;
set;
}
private RotationCycle Cycle {
get;
set;
}
= new RotationCycle();
public List<RaidableBase> Raids {
get;
}
= new List<RaidableBase>();
public Dictionary<ulong, DelaySettings> PvpDelay {
get;
}
= new Dictionary<ulong, DelaySettings>();
public Dictionary<ulong, HumanoidBrain> HumanoidBrains {
get;
set;
}
= new Dictionary<ulong, HumanoidBrain>();
private Dictionary<string, SkinInfo> Skins {
get;
}
= new Dictionary<string, SkinInfo>();
private Dictionary<NetworkableId, BMGELEVATOR> _elevators {
get;
set;
}
= new Dictionary<NetworkableId, BMGELEVATOR>();
public static StoredData data {
get;
set;
}
= new StoredData();
public TemporaryData temporaryData {
get;
set;
}
= new TemporaryData();
private static StringBuilder _sb {
get;
set;
}
private static StringBuilder _sb2 {
get;
set;
}
private bool wiped {
get;
set;
}
private bool buyableEnabled {
get;
set;
}
private float lastSpawnRequestTime {
get;
set;
}
private bool IsUnloading {
get;
set;
}
private bool IsShuttingDown {
get;
set;
}
private bool bypassRestarting {
get;
set;
}
private BuildingTables Buildings {
get;
set;
}
private bool DebugMode {
get;
set;
}
private int despawnLimit {
get;
set;
}
= 10;
private Dictionary<string, ItemDefinition> _deployables {
get;
set;
}
= new Dictionary<string, ItemDefinition>();
private Dictionary<ItemDefinition, string> _definitions {
get;
set;
}
= new Dictionary<ItemDefinition, string>();
private List<uint> ExcludedMounts {
get;
set;
}
= new List<uint> {
3552983236, 4218596772, 1845856065, 1992774774, 629849447, 4267988016, 1418740895, 2493676858, 3814928951, 1980628900, 703403829, 3061223907, 113644298, 3691382632, 3858860623, 286221745, 2230162530, 51176708, 3363531184, 3224878175
}
;
private List<string> Blocks {
get;
set;
}
= new List<string> {
"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 {
get;
set;
}
= new List<string> {
"spikes.floor", "barricade.metal", "barricade.woodwire", "barricade.wood", "wall.external.high.wood", "wall.external.high.stone", "wall.external.high.ice"
}
;
private List<string> arguments {
get;
set;
}
= new List<string> {
"add", "remove", "list", "clean", "easy", "med", "medium", "hard", "expert", "nightmare", "0", "1", "2", "3", "4", "toggle", "stability"
}
;
private List<string> unityEngineScripts {
get;
set;
}
= new List<string> {
"saddletest", "fuel_storage"
}
;
private SkinSettingsImportedWorkshop ImportedWorkshopSkins {
get;
set;
}
private List<string> _lockoutKeys = new List<string>();
private const float M_RADIUS = 25f;
private const float CELL_SIZE = M_RADIUS / 2f;
private List<Coroutine> loadCoroutines = new List<Coroutine>();
private Dictionary<string, PasteData> _pasteData = new Dictionary<string, PasteData>();
private readonly IPlayer _consolePlayer = new Game.Rust.Libraries.Covalence.RustConsolePlayer();
private List<BaseEntity.Slot> _checkSlots = new List<BaseEntity.Slot> {
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 PasteData Get(string baseName) {
PasteData pasteData;
if (!Instance._pasteData.TryGetValue(baseName, out pasteData)) {
Instance._pasteData[baseName] = pasteData = new PasteData();
}
return pasteData;
}
}
private List<RankedRecord> RankedRecords = new List<RankedRecord> {
new RankedRecord("raidablebases.ladder.easy", "raideasy", RaidableMode.Easy), new RankedRecord("raidablebases.ladder.medium", "raidmedium", RaidableMode.Medium), new RankedRecord("raidablebases.ladder.hard", "raidhard", RaidableMode.Hard), new RankedRecord("raidablebases.ladder.expert", "raidexpert", RaidableMode.Expert), new RankedRecord("raidablebases.ladder.nightmare", "raidnightmare", RaidableMode.Nightmare), new RankedRecord("raidablebases.th", "raidhunter", RaidableMode.Points),
}
;
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 {
[JsonIgnore]
public Coroutine co;
public HashSet<NetworkableId> NID {
get;
set;
}
= new HashSet<NetworkableId>();
public TemporaryData() {
}
public void StartCoroutine(List<NetworkableId> tmp) {
co = ServerMgr.Instance.StartCoroutine(Purge(tmp));
}
public void StopCoroutine() {
if (co != null) {
ServerMgr.Instance.StopCoroutine(co);
}
}
public IEnumerator Purge(List<NetworkableId> tmp) {
var instruction = CoroutineEx.waitForSeconds(0.025f);
var checks = 0;
while (tmp.Count > 0) {
if (++checks % 25 == 0) {
yield return instruction;
}
var uid = tmp[0];
NID.Remove(uid);
tmp.Remove(uid);
BaseNetworkable.serverEntities.Find(uid).SafelyKill();
}
co = null;
}
}
public class StoredData {
[JsonProperty(PropertyName = "PlayerLockouts")]
public Dictionary<string, Lockout> Lockouts {
get;
}
= new Dictionary<string, Lockout>();
public Dictionary<string, PlayerInfo> Players {
get;
set;
}
= new Dictionary<string, PlayerInfo>();
public Dictionary<ulong, BuyableInfo> BuyableCooldowns {
get;
set;
}
= new Dictionary<ulong, BuyableInfo>();
public string RaidTime {
get;
set;
}
= DateTime.MinValue.ToString();
public int TotalEvents {
get;
set;
}
public StoredData() {
}
}
public class SpawnComparer : IComparer<RaidableSpawnLocation> {
private Vector3 a;
public SpawnComparer(Vector3 a) {
this.a = a;
}
int IComparer<RaidableSpawnLocation>.Compare(RaidableSpawnLocation x, RaidableSpawnLocation y) {
return (x.Location - a).sqrMagnitude.CompareTo((y.Location - a).sqrMagnitude);
}
}
public class RandomBase {
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;
public float Radius;
public float heightAdj;
public float typeDistance;
public float protectionRadius;
public float safeRadius;
public float buildRadius;
public int attempts;
public bool autoHeight;
public bool stability;
public bool checkTerrain;
public bool Sorted;
public bool IsUnloading;
public RandomBase() {
}
public RandomBase(string BaseName, BaseProfile Profile, Vector3 Position, RaidableType Type, RaidableSpawns spawns, Payments payments) {
this.BaseName = BaseName;
this.Position = Position;
this.Profile = Profile;
this.type = Type;
this.spawns = spawns;
this.payments = payments;
this.Radius = Profile.Options.ProtectionRadius(Type);
}
public BuildingOptions options => Profile.Options;
public bool isBuyableEvent => payments != null && payments.position != Vector3.zero;
public bool hasSpawns => spawns.Spawns.Count > 0 || spawns.Seabed.Count > 0 && options.Water.SpawnOnSeabed;
public void TrySortByDistance() {
if (config.Settings.Buyable.Closest && !Sorted && isBuyableEvent) {
Message(owner, "BuyBaseLocate");
var positions = spawns.GetLocations(options.Water.CacheType).ToList();
positions.Sort(new SpawnComparer(payments.position));
if (options.Water.CacheType == CacheType.Seabed) {
spawns.Seabed = new HashSet<RaidableSpawnLocation>(positions);
}
else spawns.Spawns = new HashSet<RaidableSpawnLocation>(positions);
Sorted = true;
}
else Sorted = false;
}
}
public class BackpackData {
public DroppedItemContainer backpack;
public BasePlayer _player;
public ulong userID;
public BasePlayer player => _player ?? (_player = RustCore.FindPlayerById(userID));
}
public class BuyableInfo {
public string Easy, Medium, Hard, Expert, Nightmare;
private static double GetTimeRemaining(ulong userid, RaidableMode mode) {
var diff = mode == RaidableMode.Easy ? config.Settings.Buyable.Cooldowns.Easy : mode == RaidableMode.Medium ? config.Settings.Buyable.Cooldowns.Medium : mode == RaidableMode.Hard ? config.Settings.Buyable.Cooldowns.Hard : mode == RaidableMode.Expert ? config.Settings.Buyable.Cooldowns.Expert : config.Settings.Buyable.Cooldowns.Nightmare;
if (diff.Cooldown <= 0) return 0;
BuyableInfo bi;
if (!data.BuyableCooldowns.TryGetValue(userid, out bi)) return 0f;
var dateString = mode == RaidableMode.Easy ? bi.Easy : mode == RaidableMode.Medium ? bi.Medium : mode == RaidableMode.Hard ? bi.Hard : mode == RaidableMode.Expert ? bi.Expert : bi.Nightmare;
DateTime date;
if (!DateTime.TryParse(dateString, out date)) return 0;
double time = date.Subtract(DateTime.Now).TotalSeconds;
return time > 0 ? time : 0;
}
public static double GetTimeRemaining(BasePlayer buyer, RaidableMode mode, bool message) {
double time = GetTimeRemaining(buyer.userID, mode);
if (time > 0) {
if (message) {
Message(buyer, "BuyCooldown", Math.Ceiling(time));
}
return time;
}
return 0;
}
public static double GetTimeRemaining(ulong userid) {
foreach (RaidableMode mode in GetRaidableModes()) {
double time = GetTimeRemaining(userid, mode);
if (time > 0) {
return time;
}
}
data.BuyableCooldowns.Remove(userid);
return 0;
}
}
public class DelaySettings {
public RaidableBase RaidableBase;
public Timer Timer;
public bool AllowPVP;
public float time;
public static bool CanRemoveOwner(DroppedItemContainer backpack) {
DelaySettings ds;
if (!Instance.PvpDelay.TryGetValue(backpack.playerSteamID, out ds)) return false;
return ds.AllowPVP && config.Settings.Management.BackpacksPVP || !ds.AllowPVP && config.Settings.Management.BackpacksPVE;
}
}
public class MountInfo {
public Vector3 position;
public float radius;
public BaseMountable mountable;
}
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 List<ulong>();
public List<ulong> workshopSkins = new List<ulong>();
public List<ulong> importedSkins = new List<ulong>();
public List<ulong> allSkins = new List<ulong>();
}
public class Lockout {
public double Easy, Medium, Hard, Expert, Nightmare;
public Lockout() {
}
public bool Any() => Easy > 0 || Medium > 0 || Hard > 0 || Expert > 0 || Nightmare > 0;
}
public class RankedRecord {
public string Permission;
public string Group;
public RaidableMode Mode;
public RankedRecord(string permission, string group, RaidableMode mode) {
Permission = permission;
Group = group;
Mode = mode;
}
}
public class LandLevel {
public float Min;
public float Max;
}
public class RaidableSpawnLocation {
public List<Vector3> Surroundings = new List<Vector3>();
public Vector3 Location = Vector3.zero;
public float WaterHeight;
public float TerrainHeight;
public float SpawnHeight;
public float Radius;
public bool AutoHeight;
public RaidableSpawnLocation(Vector3 Location) {
this.Location = Location;
}
}
public class ZoneInfo {
internal Vector3 Position;
internal Vector3 Size;
internal Vector3 extents;
internal Matrix4x4 m;
internal float Distance;
internal bool isBlocked;
public ZoneInfo(object location, object radius, object size, bool isBlocked) {
var dist = Mathf.Max(config.Settings.ZoneDistance, 100f);
this.isBlocked = isBlocked;
Position = (Vector3)location;
if (radius is float) {
Distance = (float)radius + M_RADIUS + dist;
}
if (size is Vector3 && !size.Equals(Vector3.zero)) {
Size = (Vector3)size + new Vector3(dist, Position.y + 100f, dist);
extents = Size * 0.5f;
m = Matrix4x4.TRS(Position, new Quaternion(), Vector3.one);
}
}
public bool IsPositionInZone(Vector3 vector) {
if (Size != Vector3.zero) {
var v = m.inverse.MultiplyPoint3x4(vector);
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, vector, Distance);
}
}
public class BaseProfile {
public List<LootItem> BaseLootList {
get;
set;
}
= new List<LootItem>();
public BuildingOptions Options {
get;
set;
}
= new BuildingOptions();
public RaidableSpawns Spawns {
get;
set;
}
= new RaidableSpawns();
public string ProfileName {
get;
set;
}
public BaseProfile() {
Options.AdditionalBases = new Dictionary<string, List<PasteOption>>();
}
public BaseProfile(BuildingOptions options, string name) {
Options = options;
ProfileName = name;
}
public static BaseProfile Clone(BaseProfile profile) {
return profile.MemberwiseClone() as BaseProfile;
}
}
private class BuildingTables {
public Dictionary<RaidableMode, List<LootItem>> DifficultyLootLists {
get;
set;
}
= new Dictionary<RaidableMode, List<LootItem>>();
public Dictionary<DayOfWeek, List<LootItem>> WeekdayLootLists {
get;
set;
}
= new Dictionary<DayOfWeek, List<LootItem>>();
public Dictionary<string, BaseProfile> Profiles {
get;
set;
}
= new Dictionary<string, BaseProfile>();
public bool IsConfigured(string baseName) => Profiles.Exists(m => m.Key == baseName || m.Value.Options.AdditionalBases.ContainsKey(baseName));
public List<string> Removed = new List<string>();
public void Remove(string baseName) {
if (!Profiles.Remove(baseName)) {
foreach (var profile in Profiles.Values) {
if (profile.Options.AdditionalBases.Remove(baseName)) {
Removed.Add(baseName);
break;
}
}
}
else Removed.Add(baseName);
}
}
public GridControllerManager GridController {
get;
set;
}
= new GridControllerManager();
public class GridControllerManager {
internal Dictionary<RaidableType, RaidableSpawns> Spawns {
get;
set;
}
= new Dictionary<RaidableType, RaidableSpawns>();
internal Coroutine gridCoroutine {
get;
set;
}
internal Coroutine fileCoroutine {
get;
set;
}
internal float gridTime {
get;
set;
}
public double GetRaidTime() => DateTime.Parse(data.RaidTime).Subtract(DateTime.Now).TotalSeconds;
public void StartAutomation() {
if (Instance.Automated.IsScheduledEnabled) {
if (data.RaidTime != DateTime.MinValue.ToString() && GetRaidTime() > config.Settings.Schedule.IntervalMax) {
data.RaidTime = DateTime.MinValue.ToString();
}
Instance.Automated.StartCoroutine(RaidableType.Scheduled);
}
if (Instance.Automated.IsMaintainedEnabled) {
Instance.Automated.StartCoroutine(RaidableType.Maintained);
}
}
private IEnumerator LoadFiles() {
yield return Instance.LoadTables();
yield return Instance.LoadProfiles();
yield return CoroutineEx.waitForSeconds(5f);
RaidableBase.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 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) {
yield return CoroutineEx.waitForSeconds(1f);
}
var gridStopwatch = Stopwatch.StartNew();
RaidableSpawns spawns = Spawns[RaidableType.Grid] = new RaidableSpawns();
gridTime = Time.realtimeSinceStartup;
yield return Instance.LoadTables();
yield return Instance.LoadProfiles();
yield return SpawnsController.SetupMonuments();
bool spawnOnSeabed = false;
int minPos = (int)(World.Size / -2f);
int maxPos = (int)(World.Size / 2f);
float maxProtectionRadius = -10000f;
float minProtectionRadius = 10000f;
float maxWaterDepthSeabed = 0f;
float minWaterDepthSeabed = 0f;
float maxWaterDepth = 0f;
float landLevel = 0.5f;
int 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) {
Puts("ERROR: Grid has failed initialization. Your configuration does not have any spawn options for beaches, inland or seabed enabled! This is invalid and cannot be done. You must set a spawns database file for EVERY event type including those which are disabled if you do not want to use the grid!");
gridCoroutine = null;
gridStopwatch.Stop();
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 ManagementSettingsLocations(Vector3.zero, 200f));
}
if (zero.radius < 200f) {
zero.radius = 200f;
}
var wtObj = Convert.ToSingle(Interface.Oxide.CallHook("GetGridWaitTime"));
var waitTime = CoroutineEx.waitForSeconds(wtObj > 0f ? wtObj : 0.0035f);
var prefabs = config.Settings.Management.BlockedPrefabs.ToDictionary(kvp => kvp.Key, kvp => kvp.Value);
var blockedMapPrefabs = Pool.Get<Dictionary<Vector3, float>>();
prefabs.Remove("test_prefab");
prefabs.Remove("test_prefab_2");
if (prefabs.Count > 0) {
foreach (var prefab in World.Serialization.world.prefabs) {
string fullname;
if (!StringPool.toString.TryGetValue(prefab.id, out fullname)) {
continue;
}
string shortprefabname = Oxide.Core.Utility.GetFileNameWithoutExtension(fullname);
float dist;
if (prefabs.TryGetValue(shortprefabname, out dist)) {
var vector = new Vector3(prefab.position.x, prefab.position.y, prefab.position.z);
blockedMapPrefabs[vector] = dist;
}
}
}
bool hasBlockedMapPrefabs = blockedMapPrefabs.Count > 0;
bool hasBlockedPositions = blockedPositions.Count > 0;
for (float x = minPos; x < maxPos; x += CELL_SIZE) {
for (float z = minPos; z < maxPos; z += CELL_SIZE) {
var position = new Vector3(x, 0f, z);
if (hasBlockedPositions && blockedPositions.Exists(a => InRange2D(position, a.position, a.radius))) {
continue;
}
if (hasBlockedMapPrefabs && SpawnsController.IsBlockedByMapPrefab(blockedMapPrefabs, position)) {
continue;
}
position.y = SpawnsController.GetSpawnHeight(position);
SpawnsController.ExtractLocation(spawns, position, landLevel, minProtectionRadius, maxProtectionRadius, minWaterDepthSeabed, maxWaterDepthSeabed, maxWaterDepth, spawnOnSeabed);
if (++checks >= 25) {
checks = 0;
yield return waitTime;
}
}
}
blockedMapPrefabs.Clear();
Pool.Free(ref blockedMapPrefabs);
RaidableBase.IsSpawnerBusy = false;
Instance.GridController.StartAutomation();
Instance.Queues.Messages.Clear();
gridStopwatch.Stop();
gridCoroutine = null;
Puts(mx("Initialized Grid", null, Math.Floor(gridStopwatch.Elapsed.TotalSeconds), gridStopwatch.Elapsed.Milliseconds, World.Size, spawns.Spawns.Count));
if (spawns.Seabed.Count > 0) Puts(mx("Initialized Grid Sea", null, spawns.Seabed.Count));
}
public void LoadSpawns() {
Spawns = new Dictionary<RaidableType, RaidableSpawns>();
Spawns.Add(RaidableType.Grid, new RaidableSpawns());
if (SpawnsFileValid(config.Settings.Manual.SpawnsFile)) {
var spawns = GetSpawnsLocations(config.Settings.Manual.SpawnsFile);
if (spawns.Count > 0) {
Puts(mx("LoadedManual", null, spawns.Count));
Spawns[RaidableType.Manual] = new RaidableSpawns(spawns);
}
}
if (SpawnsFileValid(config.Settings.Schedule.SpawnsFile)) {
var spawns = GetSpawnsLocations(config.Settings.Schedule.SpawnsFile);
if (spawns.Count > 0) {
Puts(mx("LoadedScheduled", null, spawns.Count));
Spawns[RaidableType.Scheduled] = new RaidableSpawns(spawns);
}
}
if (SpawnsFileValid(config.Settings.Maintained.SpawnsFile)) {
var spawns = GetSpawnsLocations(config.Settings.Maintained.SpawnsFile);
if (spawns.Count > 0) {
Puts(mx("LoadedMaintained", null, spawns.Count));
Spawns[RaidableType.Maintained] = new RaidableSpawns(spawns);
}
}
if (SpawnsFileValid(config.Settings.Buyable.SpawnsFile)) {
var spawns = GetSpawnsLocations(config.Settings.Buyable.SpawnsFile);
if (spawns.Count > 0) {
Puts(mx("LoadedBuyable", null, spawns.Count));
Spawns[RaidableType.Purchased] = new RaidableSpawns(spawns);
}
}
}
public bool SpawnsFileValid(string spawnsFile) {
if (string.IsNullOrEmpty(spawnsFile) || spawnsFile.Equals("none", StringComparison.OrdinalIgnoreCase)) {
return false;
}
return FileExists($"SpawnsDatabase{Path.DirectorySeparatorChar}{spawnsFile}");
}
public HashSet<RaidableSpawnLocation> GetSpawnsLocations(string spawnsFile) {
Spawnfile data;
var locations = new HashSet<RaidableSpawnLocation>();
try {
data = Interface.Oxide.DataFileSystem.ReadObject<Spawnfile>($"SpawnsDatabase{Path.DirectorySeparatorChar}{spawnsFile}");
}
catch (JsonReaderException) {
return locations;
}
if (data == null) {
return locations;
}
foreach (var element in data.spawnPoints) {
if (element.Value == null) continue;
var value = element.Value.ToString();
if (string.IsNullOrEmpty(value)) continue;
var vector = value.ToVector3();
if (vector == Vector3.zero) continue;
locations.Add(new RaidableSpawnLocation(vector));
}
return locations;
}
}
private class Spawnfile {
public Dictionary<string, object> spawnPoints = new Dictionary<string, object>();
}
public class QueueController {
internal Queue<RandomBase> queue = new Queue<RandomBase>();
internal DebugMessages Messages = new DebugMessages();
internal Coroutine _coroutine;
internal int spawnChecks;
internal bool Paused;
internal RaidableBases Instance;
internal bool Any => queue.Count > 0;
public class DebugMessages {
internal RaidableBases Instance;
public class Info {
public int amount = 1;
public List<string> values = new List<string>();
public override string ToString() {
if (values.Count > 0) {
return $": {string.Join(", ", values.ToArray())}";
}
return string.Empty;
}
}
internal Dictionary<string, Info> _elements = new Dictionary<string, Info>();
internal IPlayer DebugUser;
public string Add(string element, object obj = null) {
if (string.IsNullOrEmpty(element)) {
return null;
}
Info info;
if (!_elements.TryGetValue(element, out info)) {
if (_elements.Count >= 20) {
_elements.Remove(_elements.ElementAt(0).Key);
}
_elements[element] = info = new Info();
}
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 && IsInstanceValid && Instance.DebugMode) {
foreach (var element in _elements) {
PrintInternal(user, $"{element.Value.amount}x - {element.Key}{element.Value.ToString()}");
}
Clear();
}
}
private bool PrintInternal(IPlayer user, string message) {
if (!string.IsNullOrEmpty(message) && IsInstanceValid && Instance.DebugMode) {
if (Instance.LogSpawnToFile) {
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);
RaidableBase.IsSpawnerBusy = false;
Print(message);
Puts(message);
}
public bool Print(string message) {
Print(DebugUser, message, null);
return false;
}
public void Print(string message, object obj) {
Print(DebugUser, 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 && IsInstanceValid && Instance.DebugMode) {
PrintInternal(DebugUser, GetLast(id));
}
}
public string GetLast(string id = null) {
if (_elements.Count == 0) {
return m("CannotFindPosition", id);
}
var element = _elements.ElementAt(_elements.Count - 1);
_elements.Remove(element.Key);
return $"{element.Value.amount}x - {element.Key}{element.Value.ToString()}";
}
}
public QueueController() {
spawnChecks = Mathf.Clamp(config.Settings.Management.SpawnChecks, 1, 500);
}
public void StartCoroutine() {
StopCoroutine();
_coroutine = ServerMgr.Instance.StartCoroutine(FindEventPosition());
}
public void StopCoroutine() {
if (_coroutine != null) {
ServerMgr.Instance.StopCoroutine(_coroutine);
_coroutine = null;
}
queue.ToList().ForEach(sp => {
sp.IsUnloading = true;
sp.payments?.Refund();
}
);
queue.Clear();
}
public void Add(RandomBase sp) {
if (!queue.Contains(sp)) {
queue.Enqueue(sp);
}
}
private void Spawn(RandomBase rb, Vector3 position) {
if (!rb.IsUnloading && IsInstanceValid) {
rb.Position = position;
if (Instance.PasteBuilding(rb)) {
RaidableBase.IsSpawnerBusy = true;
CompletePurchase(rb);
Teleport(rb);
}
}
}
private void CompletePurchase(RandomBase rb) {
if (rb.type == RaidableType.Purchased) {
if (rb.owner != null) {
var grid = FormatGridReference(rb.Position);
if (rb.owner.HasPermission("raidablebases.despawn.buyraid")) {
if (config.Settings.Buyable.Refunds.Enabled) {
Message(rb.owner, "BuyRefundableBaseSpawnedAt", rb.Position, grid, config.Settings.BuyCommand, config.Settings.Buyable.Refunds.Percentage);
}
else Message(rb.owner, "BuyCancellationsBaseSpawnedAt", rb.Position, grid, config.Settings.BuyCommand);
}
else Message(rb.owner, "BuyBaseSpawnedAt", rb.Position, grid);
if (config.EventMessages.AnnounceBuy) {
foreach (var target in BasePlayer.activePlayerList) {
QueueNotification(target, "BuyBaseAnnouncement", rb.owner.displayName, rb.Position, grid);
}
}
Puts(mx("BuyBaseAnnouncementConsole", null, rb.owner.displayName, rb.Profile.Options.Mode, rb.BaseName, rb.Position, grid));
}
config.Settings.Buyable.Cooldowns.Set(rb.userid, rb.options.Mode);
}
}
private void Teleport(RandomBase rb) {
if (rb.user != null && rb.user.IsAdmin && rb.user.IsConnected) {
rb.user.Teleport(rb.Position.x, rb.Position.y, rb.Position.z);
}
}
private bool CanBypassPause(RandomBase rb) {
if (rb.type == RaidableType.Manual) {
return rb.user != null;
}
if (rb.type == RaidableType.Purchased) {
return rb.userid.HasPermission("raidablebases.canbypass");
}
return false;
}
private IEnumerator FindEventPosition() {
CacheType cacheType;
int checks = 0;
RandomBase spq;
string topology = "";
while (true) {
if (++checks >= spawnChecks) {
yield return CoroutineEx.waitForSeconds(0.025f);
checks = 0;
}
if (!queue.TryPeek(out spq)) {
yield return CoroutineEx.waitForSeconds(1f);
continue;
}
if (Instance.Buildings.Removed.Contains(spq.BaseName)) {
queue.Dequeue();
continue;
}
if (!spq.IsUnloading && spq.Position != Vector3.zero) {
Spawn(spq, spq.Position);
yield return CoroutineEx.waitForSeconds(1f);
queue.Dequeue();
continue;
}
spq.TrySortByDistance();
spq.spawns.Check();
while (spq.hasSpawns) {
if (!IsInstanceValid || spq.IsUnloading) {
_coroutine = null;
yield break;
}
if (++checks >= spawnChecks) {
if (spq.type != RaidableType.Purchased && queue.Exists(sp => sp.type == RaidableType.Purchased)) {
//queue.Dequeue();
break;
}
checks = 0;
yield return CoroutineEx.waitForSeconds(0.025f);
}
if (RaidableBase.IsSpawnerBusy || Paused && !CanBypassPause(spq)) {
yield return CoroutineEx.waitForSeconds(1f);
continue;
}
spq.attempts++;
var rsl = spq.spawns.GetRandom(spq.options.Water, spq.isBuyableEvent);
var vector = rsl.Location;
if (!TopologyChecks(spq, vector, topology)) {
continue;
}
vector.y = GetAdjustedHeight(spq, vector);
if (IsTooClose(spq, vector)) {
continue;
}
if (IsAreaManuallyBlocked(spq, vector)) {
continue;
}
if (CanSpawnCustom(spq, vector)) {
yield return CoroutineEx.waitForSeconds(1f);
break;
}
if (CanSpawnCustomMaintained(spq, vector)) {
yield return CoroutineEx.waitForSeconds(1f);
break;
}
if (CanSpawnCustomSchedule(spq, vector)) {
yield return CoroutineEx.waitForSeconds(1f);
break;
}
if (CanSpawnCustomBuyable(spq, vector)) {
yield return CoroutineEx.waitForSeconds(1f);
break;
}
if (IsSubmerged(spq, rsl, vector)) {
continue;
}
if (!IsAreaSafe(spq, rsl, vector, out cacheType)) {
continue;
}
if (!spq.pasteData.valid) {
yield return SetupCopyPasteRadius(spq);
// ... [Code preview truncated due to length. Full source in attached file] ...