/* *  <----- End-User License Agreement -----> *  Copyright © 2024 Iftebinjan *  Devoloper: Iftebinjan (Contact: https://discord.gg/HFaGs8YwsH) *  *  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. *  */ using Newtonsoft.Json; using Oxide.Core; using Oxide.Core.Libraries.Covalence; using Oxide.Core.Plugins; using Oxide.Game.Rust.Cui; using System.Globalization; using UnityEngine; using Newtonsoft.Json.Linq; using Oxide.Core.Configuration; using Oxide.Core.Libraries; using Oxide.Plugins; using System; using System.Collections.Generic; using System.Linq; using Facepunch.Math; using UnityEngine.AI; using Rust; using Time = UnityEngine.Time; using System.Collections; using UnityEngine.UIElements; using ProtoBuf; using Facepunch; namespace Oxide.Plugins { [Info("MonumentEvents", "Ifte", "1.1.1")] public class MonumentEvents : RustPlugin { /*------------------------------------ * * MonumentEvents by Ifte * Support: https://discord.gg/cCWadjcapr * Fiverr: https://www.fiverr.com/s/e2pkYY * Eror404 not found * ------------------------------------*/ /*------------------------------------ * TO-DO LIST * * ------------------------------------*/ #region Vars [PluginReference] private Plugin NpcSpawn, ZoneManager, ImageLibrary; private static MonumentEvents ins; private Configuration config; private Timer showSpawnPoints; private const string AdminPerm = "MonumentEvents.admin"; private Coroutine autoCoroutine; private Dictionary monumentEvents = new Dictionary(); private Dictionary editingMonument = new Dictionary(); private static RaycastHit raycastHit; private static NavMeshHit navmeshHit; private const int WORLD_LAYER = 65536; private static readonly string[] BlockedColliders = { "cliff", "rock", "junk", "range", "invisible" }; private static readonly string[] AcceptedColliders = { "road", "rocket_factory", "train_track", "runway", "_grounds", "concrete_slabs", "office", "industrial", "junkyard" }; private static Collider[] _buffer = new Collider[256]; #endregion #region Config & Lang private class Configuration { [JsonProperty(PropertyName = "Event Timer Interval (In Seconds)")] public int intervalTmer = 1; [JsonProperty(PropertyName = "Event Notification Timer (In Seconds)")] public int notificationTimer = 300; [JsonProperty(PropertyName = "Chat Settings")] public ChatSetting chatSetting = new ChatSetting(); [JsonProperty(PropertyName = "Scheduled Events")] public ScheduledEvents scheduledEvents = new ScheduledEvents(); public class UISetting { [JsonProperty(PropertyName = "Enable Event UI")] public bool enableEventUI = true; } public class ChatSetting { [JsonProperty(PropertyName = "Chat Avatar Icon")] public ulong avatarID = 0; [JsonProperty(PropertyName = "Chat Prefix")] public string prefix = "Monument Events -> "; [JsonProperty(PropertyName = "Chat Notification Type(CHAT/TIP/BOTH)")] public string type = "BOTH"; } public class ScheduledEvents { [JsonProperty(PropertyName = "Enabled")] public bool scheduledEvent = true; [JsonProperty(PropertyName = "Minimum Time Between Events [In Seconds]")] public int minTime = 1800; [JsonProperty(PropertyName = "Maximum Time Between Events [In Seconds]")] public int maxTime = 3600; [JsonProperty(PropertyName = "Max Events On Server At Once")] public int maxEventsServer = 3; [JsonProperty(PropertyName = "Minimum Required Players Online")] public int minimiumRequiredplayer = 1; [JsonProperty(PropertyName = "Spawn Event From The Schedule Events List")] public bool spawnFromList = false; [JsonProperty(PropertyName = "Schedule Events List", ObjectCreationHandling = ObjectCreationHandling.Replace)] public List scheduledList = new List() { "gas_station_1", "airfield_1", "launch_site_1", "trainyard_1", "junkyard_1" }; } } protected override void LoadConfig() { base.LoadConfig(); config = Config.ReadObject(); } protected override void LoadDefaultConfig() { config = new Configuration(); } protected override void SaveConfig() { Config.WriteObject(config, true); } protected override void LoadDefaultMessages() { lang.RegisterMessages(new Dictionary { ["EventStartMSG"] = "Attention! A volatile situation is unfolding at {EVENT_NAME}. Rogue scientists has seized control and are hostile to all nearby.", ["EventEndMSG"] = "{EVENT_NAME} event has concluded!", ["EventCrateLanded"] = "Chinook crates have been air-dropped onto {EVENT_NAME}. Secure them before others do.", ["EventEndingSoon"] = "{EVENT_NAME} event will end in in {TIMER} sec!", ["EventPatrolHeliCover"] = "Beware! Patrol helicopters are actively patrolling {EVENT_NAME}. Proceed with extreme caution.", ["EventCratedHackStart"] = "Alert! Someone is trying to hack the crates on {EVENT_NAME}.", ["EventBackupSent"] = "Scientists have been alerted on {EVENT_NAME} and reinforcements are en route.", ["EventPreNotify"] = "{EVENT_NAME} Event starting in {NOTIFYTIME} seconds.", ["EventJoinPlayer"] = "You have entered the event area", ["EventLeavePlayer"] = "You have left the event area", }, this, "en"); } private string GetLang(string message) { return lang.GetMessage(message, this); } #endregion #region Data #region Settings private SavedSettings settings; private void LoadSettingsData() { settings = Interface.Oxide.DataFileSystem.ReadObject($"{Name}/SettingsData"); } private void SaveSettingsData() { Interface.Oxide.DataFileSystem.WriteObject($"{Name}/SettingsData", settings); } private class SavedSettings { [JsonProperty(PropertyName = "Monuments Settings", ObjectCreationHandling = ObjectCreationHandling.Replace)] public List monumentSettings { get; set; } = new List() { new MonumentSetting() { MonumentName = "Radtown", MonumentID = "radtown_1", enableMonument = true, timer = 1800, resetPuzzle = false, npcSetting = new NPCSetting() { spawnNpc = true, npcAmount = 20, npcIDs = new List { 101, 102, 103, 104, 201, 301, 401, 402 }, npcPositions = new List { "(-49.64, 0.16, -2.63)", "(-50.22, 0.05, 4.40)", "(-39.15, 0.14, -28.51)", "(-12.67, 0.20, -26.32)", "(10.03, 0.08, -28.57)", "(45.82, 1.01, -28.18)", "(57.40, 0.15, 3.12)", "(48.60, 0.14, -2.76)", "(34.64, 0.13, 27.05)", "(-3.57, 0.10, 24.22)", "(-19.43, 0.13, 32.68)", "(-43.28, 0.26, 31.99)", "(27.73, 7.85, 13.01)", "(35.22, 5.79, -21.78)", "(-1.84, 0.08, -7.09)", "(10.17, 0.05, 9.97)", "(-24.84, 0.03, -13.19)", "(-25.45, 0.26, 10.32)", "(29.29, 1.01, -12.03)", "(36.73, 0.14, 8.04)", "(-11.37, 4.88, 25.90)", "(-39.28, 8.41, 20.72)", "(-39.03, 0.25, -9.53)" } }, crateSetting = new CratesSetting() { spawnCrates = true, hackableAmount = 1, hackableLocations = new List() { "(7.92, 0.20, 0.49)", "(-3.49, 0.19, 0.88)" }, }, patrolSetting = new PatrolHeliSetting() { spawnPaHeli = false, patrolHeliAmount = 1 }, waveSetting = new WaveSetting() { enableWave = true, npcAmount = 20, npcIDs = new List() { 101, 102, 103, 104, 201, 301, 401, 402 }, npcDropLocations = new List() { "(66.94, 0.49, 0.20)", "(-60.08, 0.11, 1.22)" }, patrolAmount = 0, }, domeSetting = new DomeSetting() { enableSphere = true, sphereAmount = 3, sphereRadius = 120, }, mapMarker = new MarkerSetting() { markerColorOutline = "#c5e361", }, MapNote = new MapNoteSettings() { Text = "Radtown", }, pveSetting = new PVESetting() { enablePVPSupport = true, } }, new MonumentSetting() { MonumentName = "Oxum's Gas Station", MonumentID = "gas_station_1", enableMonument = true, timer = 1800, resetPuzzle = false, npcSetting = new NPCSetting() { spawnNpc = true, npcAmount = 20, npcIDs = new List { 101, 102, 103, 104, 201, 301, 401, 402 }, npcPositions = new List { "(-6.83, 3.03, -7.87)", "(12.13, 3.03, -11.03)", "(23.03, 3.00, 8.47)", "(-7.01, 3.25, 24.17)", "(-11.97, 3.25, 29.31)", "(14.50, 3.24, 30.43)", "(22.20, 3.00, 39.22)", "(-3.25, 3.00, 39.90)", "(-7.05, 9.25, 24.36)", "(-0.30, 9.26, 16.59)", "(14.45, 9.26, 28.68)", "(3.28, 9.25, -0.23)", "(-28.36, 3.00, 24.77)", "(29.33, 2.80, -14.80)", "(4.94, 2.87, -16.21)", "(-8.87, 3.03, 1.17)", "(-21.99, 3.04, -4.11)", "(-24.41, 3.05, 5.43)", "(-17.55, 3.27, 20.64)", "(-20.55, 3.04, 34.98)", "(-8.46, 3.25, 33.08)", "(-0.62, 3.25, 29.12)", "(7.17, 3.05, 35.89)", "(25.86, 3.01, 24.40)", "(28.70, 3.01, 16.57)", "(27.48, 3.01, 2.42)", "(12.53, 3.01, -1.03)", "(-6.02, 3.01, 9.99)", "(-15.40, 9.26, 30.50)", "(-1.76, 9.27, 30.55)", "(-14.67, 9.26, 15.66)", "(17.34, 9.30, 19.98)", "(9.69, 9.26, 5.96)", "(9.53, 9.26, -7.45)", "(-0.16, 9.26, -6.96)", "(-0.92, 9.26, 6.46)" } }, crateSetting = new CratesSetting() { spawnCrates = true, hackableAmount = 1, hackableLocations = new List() { "(-18.44, 3.03, 6.17)", "(17.72, 3.04, -13.20)" }, }, patrolSetting = new PatrolHeliSetting() { spawnPaHeli = false, patrolHeliAmount = 1 }, waveSetting = new WaveSetting() { enableWave = true, npcAmount = 20, npcIDs = new List() { 101, 102, 103, 104, 201, 301, 401, 402 }, npcDropLocations = new List() { "(28.62, 3.05, 44.79)", "(-36.69, 3.02, 22.66)", "(-19.10, 2.94, -13.40)", "(33.00, 3.03, -6.45)" }, patrolAmount = 0, }, domeSetting = new DomeSetting() { enableSphere = true, sphereAmount = 3, sphereRadius = 120, }, mapMarker = new MarkerSetting() { markerColorOutline = "#ad887d", //markerText = "[Oxum's Gas Station Event]\nTime Remeaning: {TIMER}", }, MapNote = new MapNoteSettings() { Text = "Oxum's Gas Station", }, pveSetting = new PVESetting() { enablePVPSupport = true, } }, new MonumentSetting() { MonumentName = "Abandoned Supermarket", MonumentID = "supermarket_1", enableMonument = true, timer = 1800, resetPuzzle = true, npcSetting = new NPCSetting() { spawnNpc = true, npcAmount = 20, npcIDs = new List { 101, 102, 103, 104, 201, 301, 401, 402 }, npcPositions = new List { "(7.8, 0.0, 3.8)", "(-4.8, 0.0, 3.1)", "(-4.9, 0.0, -5.6)", "(8.5, 0.0, -5.1)", "(-11.3, 0.0, 2.2)", "(-3.1, 0.0, 7.2)", "(0.6, 0.0, 12.4)", "(12.4, 0.0, 9.4)", "(18.8, 0.0, -2.4)", "(5.4, 4.3, 8.5)", "(3.7, 5.5, -1.9)", "(10.7, 0.0, -16.6)", "(-11.1, 0.0, 14.7)", "(3.6, 0.0, 20.6)", "(-6.2, 5.5, -5.6)", "(15.38, 0.00, 18.91)", "(16.93, 0.00, -17.47)" } }, crateSetting = new CratesSetting() { spawnCrates = true, hackableAmount = 1, hackableLocations = new List() { "(-3.9, 0.0, -14.8)", "(0.95, 5.52, -1.82)", }, }, patrolSetting = new PatrolHeliSetting() { spawnPaHeli = false, patrolHeliAmount = 1 }, waveSetting = new WaveSetting() { enableWave = true, npcAmount = 20, npcIDs = new List() { 101, 102, 103, 104, 201, 301, 401, 402 }, npcDropLocations = new List() { "(18.87, 0.01, 16.39)", "(-14.57, 0.05, -17.87)" }, patrolAmount = 0, }, domeSetting = new DomeSetting() { enableSphere = true, sphereAmount = 3, sphereRadius = 120, }, mapMarker = new MarkerSetting() { markerColorOutline = "#ac7b5e", //markerText = "[Abandoned Supermarket Event]\nTime Remeaning: {TIMER}", }, MapNote = new MapNoteSettings() { Text = "Abandoned Supermarket", }, pveSetting = new PVESetting() { enablePVPSupport = true, } }, new MonumentSetting() { MonumentName = "Water Treatment Plant", MonumentID = "water_treatment_plant_1", enableMonument = true, timer = 1800, resetPuzzle = true, npcSetting = new NPCSetting() { spawnNpc = true, npcAmount = 40, npcIDs = new List { 101, 102, 103, 104, 201, 301, 401, 402 }, npcPositions = new List { "(97.19, 0.12, 33.61)", "(77.12, 0.74, 43.57)", "(74.45, 11.68, 48.58)", "(54.00, 16.70, 47.80)", "(106.69, 0.25, -27.59)", "(107.74, 0.29, -50.51)", "(102.98, 0.25, -83.25)", "(83.88, 0.25, -121.49)", "(84.73, 0.25, -148.92)", "(58.36, 0.25, -147.80)", "(22.19, 0.32, -152.23)", "(10.02, 0.32, -151.40)", "(-18.59, 0.28, -152.47)", "(-19.39, 0.25, -122.97)", "(-37.29, 0.22, -150.83)", "(-57.50, 0.25, -149.35)", "(-86.06, 0.24, -150.22)", "(-97.74, 0.25, -149.53)", "(-97.96, 0.25, -128.80)", "(-97.44, 0.25, -94.29)", "(-96.05, 0.00, -73.83)", "(-94.58, 0.24, -55.08)", "(-86.16, 0.24, -43.66)", "(-75.53, 0.07, -20.99)", "(-75.54, 0.07, -20.99)", "(-56.62, 19.26, 3.70)", "(-92.45, 13.42, 2.79)", "(-79.60, 20.19, 42.93)", "(-26.18, 21.88, 56.10)", "(-12.20, 6.64, 14.39)", "(44.94, 0.25, 26.34)", "(52.58, 0.32, 2.10)", "(22.90, 0.32, -16.86)", "(22.25, 0.32, -44.07)", "(-28.24, 0.32, -49.27)", "(-33.81, 0.25, -87.55)", "(8.80, 0.32, -83.73)", "(4.82, 6.27, -64.12)", "(21.52, 0.32, -82.96)", "(70.52, 6.22, -6.83)", "(0.42, 0.28, -64.76)", "(61.88, 0.29, -53.94)" } }, crateSetting = new CratesSetting() { spawnCrates = true, hackableAmount = 2, hackableLocations = new List() { "(14.881979, 0.135467529, -53.75908)", "(15.63, 0.14, -69.77)", "(3.50, 0.18, -52.41)" }, }, patrolSetting = new PatrolHeliSetting() { spawnPaHeli = true, patrolHeliAmount = 1 }, waveSetting = new WaveSetting() { enableWave = true, npcAmount = 40, npcIDs = new List() { 101, 102, 103, 104, 201, 301, 401, 402 }, npcDropLocations = new List() { "(17.04, -0.01, -115.36)", "(-75.29, 0.21, -16.77)", "(50.59, 0.33, 21.18)" }, patrolAmount = 1, }, domeSetting = new DomeSetting() { enableSphere = true, sphereAmount = 3, sphereRadius = 380, }, mapMarker = new MarkerSetting() { markerColorOutline = "#61be0f", //markerText = "[Water Treatment Plant Event]\nTime Remeaning: {TIMER}", }, MapNote = new MapNoteSettings() { Text = "Water Treatment Plant", }, pveSetting = new PVESetting() { enablePVPSupport = true, } }, new MonumentSetting() { MonumentName = "Power Plant", MonumentID = "powerplant_1", enableMonument = true, timer = 1800, resetPuzzle = true, npcSetting = new NPCSetting() { spawnNpc = true, npcAmount = 50, npcIDs = new List { 101, 102, 103, 104, 201, 301, 401, 402 }, npcPositions = new List { "(9.19, 0.32, -115.58)", "(-2.78, 0.32, -112.87)", "(1.14, 0.13, -56.92)", "(19.11, 0.28, -66.22)", "(43.20, 0.30, -54.54)", "(53.02, 0.30, -29.24)", "(76.40, 0.04, -32.47)", "(73.72, 0.01, -75.46)", "(54.44, 0.30, -97.32)", "(-24.46, 0.25, -113.40)", "(-47.83, 0.28, -112.63)", "(-56.83, 0.29, -110.85)", "(-71.55, 0.26, -93.21)", "(-90.61, 0.25, -94.10)", "(-109.84, 0.25, -82.62)", "(-110.16, 0.25, -51.24)", "(-90.78, 0.25, -20.40)", "(-85.89, 0.25, -1.89)", "(-87.30, 0.17, 20.01)", "(-73.56, 0.14, 45.13)", "(-64.93, 0.25, 82.38)", "(-36.46, 0.01, 92.03)", "(-4.82, 0.01, 92.93)", "(33.48, 0.25, 81.58)", "(50.65, 0.25, 82.18)", "(51.70, 0.28, 59.26)", "(28.94, 0.32, 35.78)", "(8.84, 0.32, 46.46)", "(-11.49, 0.25, 36.06)", "(-24.76, 3.28, 38.12)", "(-4.64, 0.25, 16.41)", "(-23.66, 0.38, -3.69)", "(-11.03, 0.30, -19.69)", "(-28.58, 0.29, -28.95)", "(-2.55, 0.32, -34.72)", "(8.56, 0.32, -27.04)", "(-23.21, 0.12, -50.63)", "(-41.02, 0.32, -41.92)", "(-38.42, 0.83, -69.24)", "(-42.30, 0.29, -87.20)", "(-77.97, 0.28, -56.22)", "(-89.49, 3.25, -79.23)", "(-79.69, 6.25, -64.52)", "(-95.67, 6.27, -69.06)", "(-75.23, 0.25, -36.65)", "(-68.65, 0.01, -14.85)", "(-71.76, 1.82, 7.21)", "(-82.06, 18.13, 20.52)", "(-65.34, 9.21, -34.60)", "(-37.91, 9.50, -37.61)", "(-20.78, 6.30, -31.33)", "(-54.49, 0.06, 24.52)", "(-46.34, 0.25, -19.44)" } }, crateSetting = new CratesSetting() { spawnCrates = true, hackableAmount = 2, hackableLocations = new List() { "(3.15, 0.14, -21.14)", "(3.14, 0.14, -14.65)", }, }, patrolSetting = new PatrolHeliSetting() { spawnPaHeli = true, patrolHeliAmount = 1 }, waveSetting = new WaveSetting() { enableWave = true, npcAmount = 40, npcIDs = new List() { 101, 102, 103, 104, 201, 301, 401, 402 }, npcDropLocations = new List() { "(3.12, 0.14, -91.72)", "(18.41, 0.18, 71.11)", "(-61.19, 0.25, 61.52)", "(-100.30, 0.25, -100.73)" }, patrolAmount = 1, }, domeSetting = new DomeSetting() { enableSphere = true, sphereAmount = 3, sphereRadius = 330, }, mapMarker = new MarkerSetting() { markerColorOutline = "#FFFFFF", //markerText = "[Power Plant Event]\nTime Remeaning: {TIMER}", }, MapNote = new MapNoteSettings() { Text = "Power Plant", }, pveSetting = new PVESetting() { enablePVPSupport = true, } }, new MonumentSetting() { MonumentName = "Ferry Terminal", MonumentID = "ferry_terminal_1", enableMonument = true, timer = 1800, resetPuzzle = true, npcSetting = new NPCSetting() { spawnNpc = true, npcAmount = 30, npcIDs = new List { 101, 102, 103, 104, 201, 301, 401, 402 }, npcPositions = new List { "(-81.26, 5.25, -17.64)", "(-74.16, 5.25, -29.94)", "(-75.18, 5.19, -47.13)", "(-60.00, 5.21, -63.16)", "(-49.33, 5.25, -53.75)", "(-45.56, 5.33, -75.71)", "(-33.57, 5.33, -76.09)", "(-23.15, 6.38, -63.72)", "(-20.21, 5.14, -53.74)", "(-38.19, 5.11, -50.45)", "(-42.42, 5.09, -32.93)", "(-51.73, 4.97, -21.09)", "(-50.58, 3.04, -3.04)", "(-35.47, 5.09, -15.35)", "(-19.12, 5.23, -22.95)", "(-7.09, 5.12, -30.38)", "(7.85, 5.25, -21.94)", "(25.17, 5.25, -20.96)", "(26.93, 5.47, -27.14)", "(13.68, 5.42, -63.36)", "(40.96, 5.25, -9.57)", "(20.61, 5.25, -5.27)", "(1.09, 5.25, -10.80)", "(-11.53, 5.25, -7.97)", "(-1.44, 5.14, 8.95)", "(-4.63, 5.08, 21.91)", "(-42.73, 5.25, 32.04)", "(-46.50, 5.25, 46.12)", "(-26.87, 5.24, 39.58)", "(-2.95, 5.12, 35.98)", "(13.84, 5.07, 32.66)", "(34.53, 5.12, 25.99)", "(46.97, 5.14, 31.58)", "(50.81, 5.25, 4.83)", "(56.38, 5.25, 12.51)", "(64.39, 5.85, 32.68)", "(72.29, 6.03, 51.42)", "(-23.55, 5.25, 9.37)" } }, crateSetting = new CratesSetting() { spawnCrates = true, hackableAmount = 1, hackableLocations = new List() { "(14.91, 5.10, 17.47)", "(22.21, 5.08, 17.16)", "(16.93, 5.15, 12.55)" } }, patrolSetting = new PatrolHeliSetting() { spawnPaHeli = false, patrolHeliAmount = 1 }, waveSetting = new WaveSetting() { enableWave = true, npcAmount = 30, npcIDs = new List() { 101, 102, 103, 104, 201, 301, 401, 402 }, npcDropLocations = new List() { "(41.50, 5.14, 14.70)", "(-46.36, 5.25, -64.58)", "(59.80, 5.65, -13.21)" }, patrolAmount = 0, }, domeSetting = new DomeSetting() { enableSphere = true, sphereAmount = 3, sphereRadius = 300, }, mapMarker = new MarkerSetting() { markerColorOutline = "#ed7c26", //markerText = "[Ferry Terminal]\nTime Remeaning: {TIMER}", }, MapNote = new MapNoteSettings() { Text = "Ferry Terminal", }, pveSetting = new PVESetting() { enablePVPSupport = true, } }, new MonumentSetting() { MonumentName = "Small Harbor", MonumentID = "harbor_2", enableMonument = true, timer = 1800, resetPuzzle = true, npcSetting = new NPCSetting() { spawnNpc = true, npcAmount = 40, npcIDs = new List { 101, 102, 103, 104, 201, 301, 401, 402 }, npcPositions = new List { "(-76.98, 3.76, 114.50)", "(-85.88, 4.00, 92.80)", "(-86.50, 4.08, 47.54)", "(-87.45, 4.04, 19.47)", "(-86.59, 4.07, -19.65)", "(-88.07, 4.08, -74.41)", "(-95.76, 3.88, -89.69)", "(-72.22, 4.00, -76.76)", "(-57.12, 4.00, -106.56)", "(-33.37, 3.89, -78.71)", "(-24.63, 4.69, -101.70)", "(-1.24, 4.00, -99.11)", "(21.82, 4.00, -103.72)", "(9.43, 4.07, -77.98)", "(-15.91, 4.00, -70.01)", "(-18.93, 4.07, -32.29)", "(-27.34, 3.91, -52.25)", "(-41.05, 4.00, -71.78)", "(-47.08, 4.00, -44.18)", "(-61.38, 4.04, -36.31)", "(-67.45, 4.00, -52.01)", "(-57.10, 4.00, -13.28)", "(-50.89, 4.02, 4.46)", "(-27.56, 4.08, 10.20)", "(-30.87, 4.00, 32.32)", "(-17.98, 1.10, 56.91)", "(-21.10, 4.00, 72.37)", "(-22.81, 4.00, 99.73)", "(-39.20, 4.00, 114.81)", "(2.72, 4.00, 115.52)", "(4.94, 4.00, 90.00)", "(15.25, 4.11, 60.57)", "(32.24, 4.00, 77.87)", "(47.96, 4.01, 77.51)", "(45.31, 4.00, 99.90)", "(39.64, 4.02, 58.40)", "(-42.50, 8.19, 46.79)", "(4.03, 4.00, 24.87)", "(29.33, 1.11, 30.95)", "(46.93, 4.00, 21.58)", "(46.18, 4.08, -18.56)", "(31.11, 4.00, 4.28)", "(27.89, 4.01, -11.67)", "(9.08, 4.03, -14.54)", "(2.13, 4.08, -30.44)", "(87.23, 3.02, -96.63)", "(117.07, 4.00, -92.81)", "(114.24, 4.01, -72.20)", "(116.10, 10.08, -64.89)", "(106.39, 10.16, -59.32)", "(110.18, 16.31, -45.81)", "(110.18, 16.31, -45.81)", "(113.21, 4.00, -17.27)", "(118.53, 4.00, -1.26)", "(97.20, 4.00, -11.39)", "(90.93, 4.00, -23.42)", "(87.11, 4.00, -44.34)", "(92.19, 4.00, 26.48)", "(87.78, 4.00, 68.79)", "(118.98, 4.03, 82.21)", "(117.79, 4.00, 46.77)", "(117.01, 10.00, 29.19)", "(94.32, 10.72, 36.21)", "(111.25, 4.07, 15.98)" } }, crateSetting = new CratesSetting() { spawnCrates = true, hackableAmount = 2, hackableLocations = new List() { "(105.46, 3.89, 29.44)", "(105.57, 3.89, -32.55)", "(39.51, 4.00, 66.81)" } }, patrolSetting = new PatrolHeliSetting() { spawnPaHeli = true, patrolHeliAmount = 1 }, waveSetting = new WaveSetting() { enableWave = true, npcAmount = 30, npcIDs = new List() { 101, 102, 103, 104, 201, 301, 401, 402 }, npcDropLocations = new List() { "(-86.35, 3.89, -85.59)", "(-37.41, 4.03, 115.78)" }, patrolAmount = 1, }, domeSetting = new DomeSetting() { enableSphere = true, sphereAmount = 3, sphereRadius = 350, }, mapMarker = new MarkerSetting() { markerColorOutline = "#c22a3b", //markerText = "[Small Harbor]\nTime Remeaning: {TIMER}", }, MapNote = new MapNoteSettings() { Text = "Small Harbor", }, pveSetting = new PVESetting() { enablePVPSupport = true, } }, new MonumentSetting() { MonumentName = "Large Harbor", MonumentID = "harbor_1", enableMonument = true, timer = 1800, resetPuzzle = true, npcSetting = new NPCSetting() { spawnNpc = true, npcAmount = 50, npcIDs = new List { 101, 102, 103, 104, 201, 301, 401, 402 }, npcPositions = new List { "(-94.68, 4.07, -79.37)", "(-99.93, 4.07, -79.60)", "(-101.64, 4.05, -67.35)", "(-109.39, 4.32, -31.22)", "(-83.10, 4.25, -18.69)", "(-64.91, 4.33, -12.26)", "(-63.57, 4.32, -30.72)", "(-52.86, 4.32, -42.12)", "(-40.31, 4.27, -58.29)", "(-27.92, 3.99, -74.45)", "(-15.40, 4.28, -71.31)", "(-24.79, 4.32, -42.17)", "(-24.41, 4.26, -26.53)", "(-25.68, 4.25, -10.00)", "(-10.56, 4.25, -4.87)", "(4.86, 4.32, -30.85)", "(23.38, 3.99, -58.10)", "(55.71, 4.09, -48.51)", "(86.09, 4.28, -61.60)", "(96.63, 4.29, -38.54)", "(107.77, 4.54, -21.40)", "(93.94, 4.25, -12.68)", "(93.62, 4.25, -3.21)", "(95.00, 4.25, 17.81)", "(75.64, 4.26, 27.42)", "(51.65, 4.25, 30.62)", "(30.67, 4.26, 35.84)", "(31.43, 4.53, 11.77)", "(-10.20, 1.33, 24.69)", "(-16.90, 1.33, 26.75)", "(-18.32, 1.39, 3.70)", "(-39.03, 1.36, 10.07)", "(-55.37, 4.26, 37.87)", "(-38.73, 4.25, 43.38)", "(-31.51, 4.26, 83.24)", "(-43.70, 4.57, 117.72)", "(-16.92, 4.26, 144.97)", "(-24.82, 1.28, 137.50)", "(-14.19, 10.57, 119.64)", "(-7.83, 22.25, 126.35)", "(-9.84, 22.28, 144.42)", "(5.08, 22.25, 144.63)", "(4.19, 22.25, 124.87)", "(20.52, 22.25, 120.04)", "(28.20, 22.25, 119.85)", "(29.22, 22.62, 132.94)", "(39.55, 4.31, 134.00)", "(88.30, 4.25, 116.11)", "(90.77, 4.70, 95.21)", "(67.05, 4.25, 83.90)", "(71.34, 2.00, 40.63)", "(47.78, 4.25, -10.54)", "(38.41, 4.30, -25.09)", "(18.76, 4.26, -20.51)", "(-2.24, 4.14, -36.40)", "(13.04, 4.25, 86.62)", "(-19.62, 4.25, 92.98)", "(-1.21, 10.31, 112.80)", "(16.12, 4.25, 115.70)" } }, crateSetting = new CratesSetting() { spawnCrates = true, hackableAmount = 1, hackableLocations = new List() { "(-14.73, 4.14, -36.46)", "(-2.28, 1.25, 88.32)", "(57.26, 4.14, -23.08)" } }, patrolSetting = new PatrolHeliSetting() { spawnPaHeli = true, patrolHeliAmount = 1 }, waveSetting = new WaveSetting() { enableWave = true, npcAmount = 40, npcIDs = new List() { 101, 102, 103, 104, 201, 301, 401, 402 }, npcDropLocations = new List() { "(-110.41, 4.14, -34.21)", "(83.36, 4.14, -24.25)" }, patrolAmount = 1, }, domeSetting = new DomeSetting() { enableSphere = true, sphereAmount = 3, sphereRadius = 350, }, mapMarker = new MarkerSetting() { markerColorOutline = "#d99351", //markerText = "[Large Harbor]\nTime Remeaning: {TIMER}", }, MapNote = new MapNoteSettings() { Text = "Large Harbor", }, pveSetting = new PVESetting() { enablePVPSupport = true, } }, new MonumentSetting() { MonumentName = "Junkyard", MonumentID = "junkyard_1", enableMonument = true, timer = 1800, resetPuzzle = true, npcSetting = new NPCSetting() { spawnNpc = true, npcAmount = 35, npcIDs = new List { 101, 102, 103, 104, 201, 301, 401, 402 }, npcPositions = new List { "(-72.76, 0.08, 11.10)", "(-75.23, 0.14, 2.15)", "(-47.73, 0.13, 8.20)", "(-16.60, 0.13, -10.57)", "(-16.20, 0.21, 9.35)", "(6.72, 0.28, 20.98)", "(19.55, 0.30, 24.99)", "(32.22, 0.23, 23.84)", "(44.55, 0.40, 8.52)", "(56.32, 0.04, 15.75)", "(46.13, 0.07, 41.90)", "(63.43, 0.20, -39.46)", "(55.41, 0.11, -50.64)", "(31.21, 0.04, -64.82)", "(-23.57, 0.17, -63.06)", "(-55.11, 0.49, -56.07)", "(-61.45, 0.66, -49.74)", "(-75.03, 0.04, -16.68)", "(-66.15, 0.22, 39.55)", "(-55.57, 0.13, 76.01)", "(-28.93, 0.11, 80.67)", "(7.49, 0.09, 72.98)", "(33.28, 0.12, 72.07)", "(26.10, 0.03, 41.31)", "(-49.04, 11.28, -4.18)", "(-46.23, 14.65, 15.66)", "(-39.97, 11.32, 19.91)", "(-21.06, 15.11, 39.86)", "(-11.00, 0.72, 73.90)", "(23.31, 0.28, -5.93)", "(0.29, 0.19, -19.89)", "(-5.53, 0.19, -61.35)", "(-5.95, 0.30, -50.09)" } }, crateSetting = new CratesSetting() { spawnCrates = true, hackableAmount = 1, hackableLocations = new List() { "(-0.15, 0.14, -10.54)", "(-17.27, 0.12, 3.54)" } }, patrolSetting = new PatrolHeliSetting() { spawnPaHeli = false, patrolHeliAmount = 1 }, waveSetting = new WaveSetting() { enableWave = true, npcAmount = 30, npcIDs = new List() { 101, 102, 103, 104, 201, 301, 401, 402 }, npcDropLocations = new List() { "(32.03, 0.11, 8.22)", "(-68.50, 0.13, 8.56)", "(-2.18, 0.13, -49.54)" }, patrolAmount = 0, }, domeSetting = new DomeSetting() { enableSphere = true, sphereAmount = 3, sphereRadius = 200, }, mapMarker = new MarkerSetting() { markerColorOutline = "#366810", //markerText = "[Junkyard]\nTime Remeaning: {TIMER}", }, MapNote = new MapNoteSettings() { Text = "Junkyard", }, pveSetting = new PVESetting() { enablePVPSupport = true, } }, new MonumentSetting() { MonumentName = "The Dome", MonumentID = "sphere_tank", enableMonument = true, timer = 1800, resetPuzzle = true, npcSetting = new NPCSetting() { spawnNpc = true, npcAmount = 30, npcIDs = new List { 101, 102, 103, 104, 201, 301, 401, 402 }, npcPositions = new List { "(-8.18, 5.80, 55.87)", "(8.44, 5.66, 58.21)", "(29.71, 5.67, 59.29)", "(53.22, 5.66, 44.04)", "(51.05, 5.52, 7.83)", "(48.14, 5.70, -4.96)", "(39.08, 5.64, -22.41)", "(17.35, 5.61, -42.94)", "(-4.87, 5.66, -43.69)", "(-29.89, 5.71, -42.54)", "(-45.21, 5.78, -15.93)", "(-42.99, 5.78, 7.33)", "(-29.34, 5.80, 36.13)", "(13.98, 23.89, 40.01)", "(38.87, 23.88, 46.21)", "(27.44, 6.04, 16.25)", "(5.95, 5.90, -2.40)", "(-16.02, 18.86, -24.89)", "(-1.68, 32.64, -4.61)", "(-10.28, 26.47, 10.88)", "(0.29, 38.08, 32.61)", "(-15.94, 43.18, -24.85)", "(-25.13, 65.18, -2.30)", "(7.47, 64.05, -18.32)", "(20.63, 65.06, -4.40)", "(-1.08, 68.20, 16.22)", "(-1.81, 68.20, -16.34)", "(-5.10, -0.01, 16.29)", "(3.67, 0.66, -10.33)" } }, crateSetting = new CratesSetting() { spawnCrates = true, hackableAmount = 1, hackableLocations = new List() { "(-5.47, 71.64, -4.60)" } }, patrolSetting = new PatrolHeliSetting() { spawnPaHeli = false, patrolHeliAmount = 1 }, waveSetting = new WaveSetting() { enableWave = true, npcAmount = 30, npcIDs = new List() { 101, 102, 103, 104, 201, 301, 401, 402 }, npcDropLocations = new List() { "(19.82, 5.67, 55.94)", "(-45.03, 5.76, -9.36)" }, patrolAmount = 0, }, domeSetting = new DomeSetting() { enableSphere = true, sphereAmount = 3, sphereRadius = 200, }, mapMarker = new MarkerSetting() { markerColorOutline = "#593753", //markerText = "[The Dome]\nTime Remeaning: {TIMER}", }, MapNote = new MapNoteSettings() { Text = "The Dome", }, pveSetting = new PVESetting() { enablePVPSupport = true, } }, new MonumentSetting() { MonumentName = "Airfield", MonumentID = "airfield_1", enableMonument = true, timer = 1800, resetPuzzle = true, npcSetting = new NPCSetting() { spawnNpc = true, npcAmount = 45, npcIDs = new List { 101, 102, 103, 104, 201, 301, 401, 402 }, npcPositions = new List { "(-162.0, 0.3, -79.8)", "(-121.3, 0.3, -27.8)", "(-113.3, 0.0, 16.4)", "(-69.8, 0.3, 38.5)", "(-77.3, 0.3, -27.6)", "(-26.7, 0.3, -23.0)", "(30.1, 0.3, -26.6)", "(96.4, 0.3, -24.1)", "(-36.3, 3.3, -85.5)", "(-19.9, 3.3, -106.4)", "(16.2, 1.8, -101.3)", "(-16.3, 1.8, -72.3)", "(15.5, 0.3, -53.7)", "(52.8, 0.2, -65.2)", "(123.4, 0.1, -64.8)", "(158.8, 0.3, -29.1)", "(113.0, 3.1, 10.4)", "(46.5, 0.3, 14.4)", "(7.0, 0.3, 16.3)", "(-29.3, 0.3, 12.3)", "(-87.49, 0.27, -89.94)", "(-68.27, 0.32, -2.48)", "(-47.21, 21.39, -82.89)", "(-1.39, 4.72, -95.60)", "(-73.16, 0.24, -56.28)", "(96.58, 3.22, -89.95)", "(52.22, 0.28, -95.53)", "(97.92, 0.64, 31.66)", "(50.67, 0.30, 55.76)", "(6.12, 0.30, 51.85)", "(-37.07, 0.30, 56.56)", "(-109.72, 3.22, 50.88)", "(-105.10, 15.23, 57.44)", "(-156.80, 0.08, 9.40)", "(-161.46, 0.00, -12.51)", "(-130.40, 0.32, -107.86)", "(-144.57, 0.26, -100.40)" } }, crateSetting = new CratesSetting() { spawnCrates = true, hackableAmount = 2, hackableLocations = new List() { "(7.47, 0.24, -27.29)", "(-58.90, 0.09, -25.08)", "(-15.61, 0.30, -57.09)" } }, patrolSetting = new PatrolHeliSetting() { spawnPaHeli = true, patrolHeliAmount = 1 }, waveSetting = new WaveSetting() { enableWave = true, npcAmount = 40, npcIDs = new List() { 101, 102, 103, 104, 201, 301, 401, 402 }, npcDropLocations = new List() { "(-138.11, 0.30, -49.61)", "(117.92, 0.30, -42.00)" }, patrolAmount = 1, }, domeSetting = new DomeSetting() { enableSphere = true, sphereAmount = 3, sphereRadius = 380, }, mapMarker = new MarkerSetting() { markerColorOutline = "#3e3557", //markerText = "[Airfield]\nTime Remeaning: {TIMER}", }, MapNote = new MapNoteSettings() { Text = "Airfield", }, pveSetting = new PVESetting() { enablePVPSupport = true, } }, new MonumentSetting() { MonumentName = "Train Yard", MonumentID = "trainyard_1", enableMonument = true, timer = 1800, resetPuzzle = true, npcSetting = new NPCSetting() { spawnNpc = true, npcAmount = 50, npcIDs = new List { 101, 102, 103, 104, 201, 301, 401, 402 }, npcPositions = new List { "(-95.3, 0.1, -14.7)", "(-70.4, 0.1, 24.4)", "(-58.9, 0.1, 30.6)", "(-67.8, 0.3, -6.6)", "(-26.6, 0.5, -6.3)", "(-30.5, 9.0, -38.3)", "(-29.0, 9.4, -56.1)", "(-45.6, 9.0, -34.9)", "(-42.4, 0.1, 10.1)", "(-2.3, 0.2, -14.8)", "(3.1, 0.2, 35.6)", "(12.7, 0.2, -24.4)", "(-1.6, 0.2, -65.2)", "(14.2, 0.2, -65.2)", "(38.8, 0.1, -14.5)", "(33.1, 1.7, 18.7)", "(28.8, 0.0, 43.0)", "(12.5, 0.2, 18.2)", "(1.4, 0.2, 71.2)", "(36.9, 0.2, -50.3)", "(68.5, 0.2, -73.3)", "(91.7, 0.3, -58.9)", "(109.0, 0.1, -14.5)", "(120.9, 0.2, -61.8)", "(78.4, 0.3, -1.8)", "(82.3, 0.3, -38.8)", "(50.3, 1.7, 26.1)", "(58.6, 0.3, 48.0)", "(100.4, 0.2, 58.6)", "(98.2, 0.1, 31.6)", "(76.9, 0.1, 30.6)", "(106.7, 0.1, 22.5)", "(94.9, 0.3, 2.5)", "(20.2, 0.2, -0.8)", "(40.17, 0.11, 68.10)", "(-22.80, -0.25, 53.78)", "(-99.00, 0.12, 11.15)", "(-55.15, 8.28, -38.22)", "(-48.69, 9.07, -64.82)", "(7.63, 15.29, -0.91)", "(84.25, 0.29, 13.34)", "(71.23, 0.30, 68.46)", "(61.63, 0.24, -27.91)", "(26.26, 16.39, -32.73)", "(39.45, 0.24, -70.04)" } }, crateSetting = new CratesSetting() { spawnCrates = true, hackableAmount = 2, hackableLocations = new List() { "(13.54, 0.26, -13.91)", "(13.18, 0.20, 12.53)", "(0.11, 1.48, -29.40)" } }, patrolSetting = new PatrolHeliSetting() { spawnPaHeli = true, patrolHeliAmount = 1 }, waveSetting = new WaveSetting() { enableWave = true, npcAmount = 30, npcIDs = new List() { 101, 102, 103, 104, 201, 301, 401, 402 }, npcDropLocations = new List() { "(-52.12, 0.32, 6.22)", "(76.14, 0.13, -75.40)", "(84.21, 0.09, 33.87)" }, patrolAmount = 1, }, domeSetting = new DomeSetting() { enableSphere = true, sphereAmount = 3, sphereRadius = 280, }, mapMarker = new MarkerSetting() { markerColorOutline = "#FFFFFF", //markerText = "[Train Yard]\nTime Remeaning: {TIMER}", }, MapNote = new MapNoteSettings() { Text = "Train Yard", }, pveSetting = new PVESetting() { enablePVPSupport = true, } }, new MonumentSetting() { MonumentName = "Satellite Dish", MonumentID = "satellite_dish", enableMonument = true, timer = 1800, resetPuzzle = true, npcSetting = new NPCSetting() { spawnNpc = true, npcAmount = 35, npcIDs = new List { 101, 102, 103, 104, 201, 301, 401, 402 }, npcPositions = new List { "(38.41, 0.01, -28.20)", "(17.76, 0.01, -26.51)", "(-77.80, 0.01, -20.60)", "(-35.41, 0.19, 17.27)", "(11.22, 0.38, 21.40)", "(-62.6, 0.2, -41.7)", "(-27.3, 5.9, -41.6)", "(-6.2, 5.9, -21.3)", "(15.1, 6.0, -7.2)", "(42.2, 6.0, -6.8)", "(63.7, 6.0, 1.9)", "(64.2, 6.1, -16.8)", "(-13.9, 6.0, -6.7)", "(-42.2, 6.0, -6.8)", "(-62.4, 6.0, -15.0)", "(-65.2, 6.1, 1.4)", "(-6.8, 6.3, 17.4)", "(-17.4, 6.1, 33.2)", "(-21.0, 6.0, 45.9)", "(-6.9, 6.1, 51.5)", "(-33.7, 0.0, -14.6)", "(-19.0, 0.0, -13.1)", "(38.7, 0.0, -0.9)", "(19.3, 0.0, -0.9)", "(41.4, 0.0, 24.0)" } }, crateSetting = new CratesSetting() { spawnCrates = true, hackableAmount = 1, hackableLocations = new List() { "(-3.05, 6.05, -1.49)" } }, patrolSetting = new PatrolHeliSetting() { spawnPaHeli = false, patrolHeliAmount = 1 }, waveSetting = new WaveSetting() { enableWave = true, npcAmount = 30, npcIDs = new List() { 101, 102, 103, 104, 201, 301, 401, 402 }, npcDropLocations = new List() { "(-71.27, 0.16, -46.06)" }, patrolAmount = 0, }, domeSetting = new DomeSetting() { enableSphere = true, sphereAmount = 3, sphereRadius = 200, }, mapMarker = new MarkerSetting() { markerColorOutline = "#4605c9", //markerText = "[Satellite Dish Event]\nTime Remeaning: {TIMER}", }, MapNote = new MapNoteSettings() { Text = "Satellite Dish", }, pveSetting = new PVESetting() { enablePVPSupport = true, } }, new MonumentSetting() { MonumentName = "Sewer Branch", MonumentID = "radtown_small_3", enableMonument = true, timer = 1800, resetPuzzle = true, npcSetting = new NPCSetting() { spawnNpc = true, npcAmount = 35, npcIDs = new List { 101, 102, 103, 104, 201, 301, 401, 402 }, npcPositions = new List { "(-46.5, 19.9, -56.6)", "(-46.1, 19.8, -34.4)", "(-21.3, 19.7, -45.2)", "(-34.2, 19.8, -38.7)", "(0.5, 18.2, -46.5)", "(22.6, 18.1, -45.7)", "(32.0, 19.2, -30.8)", "(75.7, 12.1, -5.5)", "(46.2, 7.9, -4.0)", "(17.9, 2.8, -4.9)", "(-12.1, -1.7, -11.6)", "(-29.7, 3.2, -2.7)", "(-49.6, 8.0, -5.2)", "(-76.7, 14.8, 3.6)", "(-68.9, 15.9, -26.8)", "(-24.8, 19.1, 27.5)", "(11.8, 18.2, 24.5)", "(42.7, 17.9, 27.2)", "(81.3, 14.9, 29.9)", "(59.2, 19.6, -32.0)", "(-56.6, 15.7, 14.0)", "(-7.83, 19.48, 32.72)", "(-8.87, 18.35, 14.99)", "(30.25, 16.96, 5.17)", "(-17.70, 14.43, -23.42)", "(-24.72, 19.67, -54.96)", "(-12.41, 18.25, -34.85)" } }, crateSetting = new CratesSetting() { spawnCrates = true, hackableAmount = 1, hackableLocations = new List() { "(-23.15, 18.44, 13.61)", "(-46.80, 19.79, -44.96)" } }, patrolSetting = new PatrolHeliSetting() { spawnPaHeli = false, patrolHeliAmount = 1 }, waveSetting = new WaveSetting() { enableWave = true, npcAmount = 30, npcIDs = new List() { 101, 102, 103, 104, 201, 301, 401, 402 }, npcDropLocations = new List() { "(9.77, 18.08, -31.37)", "(3.77, 19.05, 31.92)" }, patrolAmount = 0, }, domeSetting = new DomeSetting() { enableSphere = true, sphereAmount = 3, sphereRadius = 200, }, mapMarker = new MarkerSetting() { markerColorOutline = "#111580", //markerText = "[Sewer Branch Event]\nTime Remeaning: {TIMER}", }, MapNote = new MapNoteSettings() { Text = "Sewer Branch", }, pveSetting = new PVESetting() { enablePVPSupport = true, } }, new MonumentSetting() { MonumentName = "Launch Site", MonumentID = "launch_site_1", enableMonument = true, timer = 1800, resetPuzzle = true, npcSetting = new NPCSetting() { spawnNpc = true, npcAmount = 60, npcIDs = new List { 101, 102, 103, 104, 201, 301, 401, 402 }, npcPositions = new List { "(89.52, 7.00, 86.47)", "(114.33, 19.39, 91.53)", "(138.05, 7.60, 86.52)", "(157.74, 7.00, 86.82)", "(190.71, 3.25, 99.52)", "(197.52, 12.00, 79.57)", "(239.26, 3.00, 95.02)", "(254.63, 3.25, 43.35)", "(253.30, 3.00, 75.80)", "(203.12, 3.79, 53.91)", "(185.01, 3.26, 30.23)", "(155.41, 12.00, 32.66)", "(155.53, 12.00, 58.91)", "(117.52, 12.00, 31.92)", "(103.72, 12.00, 57.79)", "(118.19, 12.00, 55.01)", "(187.52, 20.75, 25.40)", "(262.01, 20.75, 25.36)", "(217.63, 3.00, 37.09)", "(90.06, 3.00, -6.13)", "(91.21, 3.00, 39.57)", "(116.19, 3.25, -46.59)", "(107.07, 3.00, -21.85)", "(143.83, 3.25, -63.64)", "(163.28, 3.00, -53.59)", "(131.38, 19.75, -58.35)", "(113.77, 19.75, -93.91)", "(142.18, 19.78, -92.92)", "(101.96, 3.00, -108.08)", "(146.15, 11.00, -128.42)", "(164.50, 3.00, -153.44)", "(193.23, 3.01, -144.86)", "(183.01, 11.00, -127.99)", "(253.13, 3.00, -105.71)", "(253.46, 3.00, -77.48)", "(220.00, 3.00, -97.75)", "(180.42, 3.25, -90.27)", "(182.10, 3.25, -62.09)", "(207.68, 3.26, -31.04)", "(249.67, 3.25, -31.48)", "(228.65, 4.72, -50.52)", "(262.40, 20.75, -22.51)", "(225.87, 20.75, -25.26)", "(203.80, 22.75, -49.60)", "(221.86, 22.75, -58.38)", "(221.29, 22.75, -76.39)", "(227.18, 13.75, -75.96)", "(179.83, 3.00, 2.58)", "(148.36, 3.00, -20.53)", "(152.59, 3.10, 25.63)", "(159.65, 15.64, -31.08)", "(97.69, 3.00, -71.02)", "(212.10, 3.00, -141.66)", "(131.59, 10.97, -146.56)", "(137.77, 3.00, -143.23)", "(119.77, 3.00, 8.72)", "(135.20, 3.00, -13.14)", "(-49.9, 34.4, -30.7)", "(-50.4, 10.4, -32.8)", "(-39.2, 3.2, 0.3)", "(-13.3, 3.0, -46.0)", "(-88.9, 3.0, -42.4)", "(-17.1, 3.0, 38.8)", "(-0.4, -3.0, 10.8)", "(-50.3, 2.9, -55.1)", "(-57.0, 3.0, 9.3)", "(-60.3, 3.2, -9.4)", "(-41.8, -3.0, 41.4)", "(-41.1, -3.0, -44.4)", "(-52.5, -3.0, 9.2)", "(-50.5, -3.0, -12.0)", "(-58.1, -3.0, -0.1)", "(-10.2, 3.2, -0.3)" } }, crateSetting = new CratesSetting() { spawnCrates = true, hackableAmount = 3, hackableLocations = new List() { "(152.33, 3.03, 0.01)", "(-22.34, 3.19, -0.19)" } }, patrolSetting = new PatrolHeliSetting() { spawnPaHeli = true, patrolHeliAmount = 1 }, waveSetting = new WaveSetting() { enableWave = true, npcAmount = 40, npcIDs = new List() { 101, 102, 103, 104, 201, 301, 401, 402 }, npcDropLocations = new List() { "(50.73, 3.11, 0.11)", "(124.70, 3.03, -0.10)" }, patrolAmount = 1, }, domeSetting = new DomeSetting() { enableSphere = true, sphereAmount = 3, sphereRadius = 600, }, mapMarker = new MarkerSetting() { markerColorOutline = "#7ab593", //markerText = "[Launch Site Event]\nTime Remeaning: {TIMER}", }, MapNote = new MapNoteSettings() { Text = "Launch Site", }, pveSetting = new PVESetting() { enablePVPSupport = true, } }, }; public class MonumentSetting { [JsonProperty(PropertyName = "Monument Name")] public string MonumentName { get; set; } [JsonProperty(PropertyName = "Enable Monument")] public bool enableMonument { get; set; } [JsonProperty(PropertyName = "Monument ID")] public string MonumentID { get; set; } [JsonProperty(PropertyName = "Timer")] public int timer { get; set; } [JsonProperty(PropertyName = "Monument Reset Puzzle")] public bool resetPuzzle { get; set; } [JsonProperty(PropertyName = "NPC Setting")] public NPCSetting npcSetting { get; set; } [JsonProperty(PropertyName = "PatrolHeli Setting")] public PatrolHeliSetting patrolSetting { get; set; } [JsonProperty(PropertyName = "Crate Setting")] public CratesSetting crateSetting { get; set; } [JsonProperty(PropertyName = "Waves Setting")] public WaveSetting waveSetting { get; set; } [JsonProperty(PropertyName = "Map Marker Setting")] public MarkerSetting mapMarker { get; set; } [JsonProperty(PropertyName = "Map Note Setting")] public MapNoteSettings MapNote { get; set; } [JsonProperty(PropertyName = "Sphere Dome Setting")] public DomeSetting domeSetting { get; set; } [JsonProperty(PropertyName = "PVE Setting")] public PVESetting pveSetting { get; set; } } public class MapNoteSettings { [JsonProperty("Use Map Note")] public bool Enabled { get; set; } = true; [JsonProperty("Icon type (0 - Circle | 1 - Triangle)")] public int IconType { get; set; } = 1; [JsonProperty("Icon index")] public int IconIndex { get; set; } = 4; [JsonProperty("Icon color (0 - Yellow | 1 - Blue | 2 - Green | 3 - Red | 4 - Purple | 5 - Cyan)")] public int IconColor { get; set; } = 3; [JsonProperty("Text")] public string Text { get; set; } } public class CratesSetting { [JsonProperty(PropertyName = "Spawn Crates")] public bool spawnCrates { get; set; } [JsonProperty(PropertyName = "Spawn CodeLockedHackableCrate Amount")] public int hackableAmount { get; set; } [JsonProperty(PropertyName = "CodeLockedHackableCrate Timer")] public int spawnHackableCrateTimer = 300; [JsonProperty(PropertyName = "CodeLockedHackableCrate Location", ObjectCreationHandling = ObjectCreationHandling.Replace)] public List hackableLocations { get; set; } } public class NPCSetting { [JsonProperty(PropertyName = "Spawn Npc")] public bool spawnNpc { get; set; } [JsonProperty(PropertyName = "NPC Spawn Amount")] public int npcAmount { get; set; } [JsonProperty(PropertyName = "NPCs to Use From Data", ObjectCreationHandling = ObjectCreationHandling.Replace)] public List npcIDs { get; set; } [JsonProperty(PropertyName = "NPCs Spawn Positions", ObjectCreationHandling = ObjectCreationHandling.Replace)] public List npcPositions { get; set; } } public class PatrolHeliSetting { [JsonProperty(PropertyName = "Patrol Helicopters")] public bool spawnPaHeli { get; set; } [JsonProperty(PropertyName = "Patrol Helicopters Amount")] public int patrolHeliAmount { get; set; } } public class WaveSetting { [JsonProperty(PropertyName = "Enable Waves")] public bool enableWave { get; set; } [JsonProperty(PropertyName = "NPC Spawn Amount")] public int npcAmount { get; set; } [JsonProperty(PropertyName = "NPCs to Use From Data", ObjectCreationHandling = ObjectCreationHandling.Replace)] public List npcIDs { get; set; } [JsonProperty(PropertyName = "NPCs drop location", ObjectCreationHandling = ObjectCreationHandling.Replace)] public List npcDropLocations { get; set; } [JsonProperty(PropertyName = "Patrol Helicopter Amount")] public int patrolAmount { get; set; } } public class MarkerSetting { [JsonProperty(PropertyName = "Enable Marker")] public bool enableMarker { get; set; } = true; [JsonProperty(PropertyName = "Marker Radius")] public float markerRadius { get; set; } = 1.0f; /*[JsonProperty(PropertyName = "Marker Text")] public string markerText { get; set; }*/ [JsonProperty(PropertyName = "Marker Transparency")] public float markerTransparency = 0.75f; [JsonProperty(PropertyName = "Marker Color")] public string markerColor = "#000000"; [JsonProperty(PropertyName = "Marker Outline Color")] public string markerColorOutline = "#ff0000"; } public class DomeSetting { [JsonProperty(PropertyName = "Enable Sphere Dome")] public bool enableSphere { get; set; } [JsonProperty(PropertyName = "Sphere Dome Amount")] public int sphereAmount { get; set; } [JsonProperty(PropertyName = "Sphere Dome Radius")] public float sphereRadius { get; set; } } public class PVESetting { [JsonProperty(PropertyName = "Enable PVP On Event? It will automatically create a zone and enable PVP on the zone then remove the zone when event ends. (Require: ZoneManager & (SimplePVE or TruePVE))")] public bool enablePVPSupport { get; set; } } } private SavedSettings.MonumentSetting FindMonumentConfig(string name) { foreach (SavedSettings.MonumentSetting mon in settings.monumentSettings) { if (mon.MonumentID == name) return mon; } return null; } #endregion #region NPCdata private NPCData npcData; private class NPCData { public List NPCs = new List(); } private void LoadNPCData() { npcData = Interface.Oxide.DataFileSystem.ReadObject($"{Name}/NPCData"); CreateNewNPCConfig(); } private void SaveNPCData() { Interface.Oxide.DataFileSystem.WriteObject($"{Name}/NPCData", npcData); } public class NpcConfig { [JsonProperty("ID")] public int ID { get; set; } [JsonProperty("Names")] public List Names { get; set; } [JsonProperty("Health")] public float Health { get; set; } [JsonProperty("Roam Range")] public float RoamRange { get; set; } [JsonProperty("Chase Range")] public float ChaseRange { get; set; } [JsonProperty("Attack Range Multiplier")] public float AttackRangeMultiplier { get; set; } [JsonProperty("Sense Range")] public float SenseRange { get; set; } [JsonProperty("Target Memory Duration [sec.]")] public float MemoryDuration { get; set; } [JsonProperty("Scale damage")] public float DamageScale { get; set; } [JsonProperty("Aim Cone Scale")] public float AimConeScale { get; set; } [JsonProperty("Detect the target only in the NPC's viewing vision cone?")] public bool CheckVisionCone { get; set; } [JsonProperty("Vision Cone")] public float VisionCone { get; set; } [JsonProperty("Speed")] public float Speed { get; set; } [JsonProperty("Disable radio effects?")] public bool DisableRadio { get; set; } [JsonProperty("Is this a stationary NPC?")] public bool Stationary { get; set; } [JsonProperty("Remove a corpse after death?")] public bool IsRemoveCorpse { get; set; } [JsonProperty("Wear items")] public List WearItems { get; set; } [JsonProperty("Belt items")] public List BeltItems { get; set; } [JsonProperty("Kits ")] public List Kits { get; set; } } public class NpcBelt { [JsonProperty("ShortName")] public string ShortName { get; set; } [JsonProperty("Amount")] public int Amount { get; set; } [JsonProperty("SkinID (0 - default)")] public ulong SkinID { get; set; } [JsonProperty("Mods")] public List Mods { get; set; } [JsonProperty("Ammo")] public string Ammo { get; set; } } public class NpcWear { [JsonProperty("ShortName")] public string ShortName { get; set; } [JsonProperty("SkinID (0 - default)")] public ulong SkinID { get; set; } } private void CreateNewNPCConfig() { if (npcData.NPCs.Count > 0) return; //assults npcData.NPCs.Add(new NpcConfig() { ID = 101, Names = new List { "Scientist" }, Health = 200f, RoamRange = 30f, ChaseRange = 100f, AttackRangeMultiplier = 1f, SenseRange = 80f, MemoryDuration = 10f, DamageScale = 2.0f, AimConeScale = 1f, CheckVisionCone = false, VisionCone = 135f, Speed = 7.5f, DisableRadio = false, Stationary = false, IsRemoveCorpse = true, WearItems = new List { new NpcWear { ShortName = "coffeecan.helmet", SkinID = 0 }, new NpcWear { ShortName = "hoodie", SkinID = 0 }, new NpcWear { ShortName = "roadsign.jacket", SkinID = 0 }, new NpcWear { ShortName = "pants", SkinID = 0 }, new NpcWear { ShortName = "roadsign.kilt", SkinID = 0 }, new NpcWear { ShortName = "roadsign.gloves", SkinID = 0 }, new NpcWear { ShortName = "shoes.boots", SkinID = 0 }, }, BeltItems = new List { new NpcBelt { ShortName = "rifle.m39", Amount = 1, SkinID = 0, Mods = new List { "weapon.mod.holosight", "weapon.mod.flashlight" }, Ammo = string.Empty }, new NpcBelt { ShortName = "syringe.medical", Amount = 10, SkinID = 0, Mods = new List(), Ammo = string.Empty }, new NpcBelt { ShortName = "grenade.f1", Amount = 10, SkinID = 0, Mods = new List(), Ammo = string.Empty }, } }); npcData.NPCs.Add(new NpcConfig() { ID = 102, Names = new List { "Scientist" }, Health = 200f, RoamRange = 30f, ChaseRange = 100f, AttackRangeMultiplier = 1f, SenseRange = 80f, MemoryDuration = 10f, DamageScale = 2.0f, AimConeScale = 1f, CheckVisionCone = false, VisionCone = 135f, Speed = 7.5f, DisableRadio = false, Stationary = false, IsRemoveCorpse = true, WearItems = new List { new NpcWear { ShortName = "coffeecan.helmet", SkinID = 0 }, new NpcWear { ShortName = "hoodie", SkinID = 0 }, new NpcWear { ShortName = "roadsign.jacket", SkinID = 0 }, new NpcWear { ShortName = "pants", SkinID = 0 }, new NpcWear { ShortName = "roadsign.kilt", SkinID = 0 }, new NpcWear { ShortName = "roadsign.gloves", SkinID = 0 }, new NpcWear { ShortName = "shoes.boots", SkinID = 0 }, }, BeltItems = new List { new NpcBelt { ShortName = "rifle.lr300", Amount = 1, SkinID = 0, Mods = new List { "weapon.mod.holosight", "weapon.mod.flashlight" }, Ammo = string.Empty }, new NpcBelt { ShortName = "syringe.medical", Amount = 10, SkinID = 0, Mods = new List(), Ammo = string.Empty }, new NpcBelt { ShortName = "grenade.f1", Amount = 10, SkinID = 0, Mods = new List(), Ammo = string.Empty }, } }); npcData.NPCs.Add(new NpcConfig() { ID = 103, Names = new List { "Scientist" }, Health = 200f, RoamRange = 30f, ChaseRange = 100f, AttackRangeMultiplier = 1f, SenseRange = 80f, MemoryDuration = 10f, DamageScale = 2.0f, AimConeScale = 1f, CheckVisionCone = false, VisionCone = 135f, Speed = 7.5f, DisableRadio = false, Stationary = false, IsRemoveCorpse = true, WearItems = new List { new NpcWear { ShortName = "metal.plate.torso", SkinID = 0 }, new NpcWear { ShortName = "hoodie", SkinID = 0 }, new NpcWear { ShortName = "metal.facemask", SkinID = 0 }, new NpcWear { ShortName = "pants", SkinID = 0 }, new NpcWear { ShortName = "roadsign.kilt", SkinID = 0 }, new NpcWear { ShortName = "roadsign.gloves", SkinID = 0 }, new NpcWear { ShortName = "shoes.boots", SkinID = 0 }, }, BeltItems = new List { new NpcBelt { ShortName = "rifle.lr300", Amount = 1, SkinID = 0, Mods = new List { "weapon.mod.holosight", "weapon.mod.flashlight" }, Ammo = string.Empty }, new NpcBelt { ShortName = "syringe.medical", Amount = 10, SkinID = 0, Mods = new List(), Ammo = string.Empty }, new NpcBelt { ShortName = "grenade.f1", Amount = 10, SkinID = 0, Mods = new List(), Ammo = string.Empty }, } }); npcData.NPCs.Add(new NpcConfig() { ID = 104, Names = new List { "Scientist" }, Health = 200f, RoamRange = 30f, ChaseRange = 100f, AttackRangeMultiplier = 1f, SenseRange = 80f, MemoryDuration = 10f, DamageScale = 2.0f, AimConeScale = 1f, CheckVisionCone = false, VisionCone = 135f, Speed = 7.5f, DisableRadio = false, Stationary = false, IsRemoveCorpse = true, WearItems = new List { new NpcWear { ShortName = "metal.plate.torso", SkinID = 0 }, new NpcWear { ShortName = "hoodie", SkinID = 0 }, new NpcWear { ShortName = "metal.facemask", SkinID = 0 }, new NpcWear { ShortName = "pants", SkinID = 0 }, new NpcWear { ShortName = "roadsign.kilt", SkinID = 0 }, new NpcWear { ShortName = "roadsign.gloves", SkinID = 0 }, new NpcWear { ShortName = "shoes.boots", SkinID = 0 }, }, BeltItems = new List { new NpcBelt { ShortName = "hmlmg", Amount = 1, SkinID = 0, Mods = new List { "weapon.mod.holosight", "weapon.mod.flashlight" }, Ammo = string.Empty }, new NpcBelt { ShortName = "syringe.medical", Amount = 10, SkinID = 0, Mods = new List(), Ammo = string.Empty }, new NpcBelt { ShortName = "grenade.f1", Amount = 10, SkinID = 0, Mods = new List(), Ammo = string.Empty }, } }); npcData.NPCs.Add(new NpcConfig() { ID = 105, Names = new List { "Scientist" }, Health = 200f, RoamRange = 30f, ChaseRange = 100f, AttackRangeMultiplier = 1f, SenseRange = 80f, MemoryDuration = 10f, DamageScale = 2.0f, AimConeScale = 1f, CheckVisionCone = false, VisionCone = 135f, Speed = 7.5f, DisableRadio = false, Stationary = false, IsRemoveCorpse = true, WearItems = new List { new NpcWear { ShortName = "metal.plate.torso", SkinID = 0 }, new NpcWear { ShortName = "hoodie", SkinID = 0 }, new NpcWear { ShortName = "metal.facemask", SkinID = 0 }, new NpcWear { ShortName = "pants", SkinID = 0 }, new NpcWear { ShortName = "roadsign.kilt", SkinID = 0 }, new NpcWear { ShortName = "roadsign.gloves", SkinID = 0 }, new NpcWear { ShortName = "shoes.boots", SkinID = 0 }, }, BeltItems = new List { new NpcBelt { ShortName = "rifle.semiauto", Amount = 1, SkinID = 0, Mods = new List { "weapon.mod.holosight", "weapon.mod.flashlight" }, Ammo = string.Empty }, new NpcBelt { ShortName = "syringe.medical", Amount = 10, SkinID = 0, Mods = new List(), Ammo = string.Empty }, new NpcBelt { ShortName = "grenade.f1", Amount = 10, SkinID = 0, Mods = new List(), Ammo = string.Empty }, } }); //sniper npcData.NPCs.Add(new NpcConfig() { ID = 201, Names = new List { "Bolti head" }, Health = 200f, RoamRange = 30f, ChaseRange = 100f, AttackRangeMultiplier = 1f, SenseRange = 80f, MemoryDuration = 10f, DamageScale = 2.0f, AimConeScale = 1f, CheckVisionCone = false, VisionCone = 135f, Speed = 7.5f, DisableRadio = false, Stationary = false, IsRemoveCorpse = true, WearItems = new List { new NpcWear { ShortName = "metal.plate.torso", SkinID = 0 }, new NpcWear { ShortName = "hoodie", SkinID = 0 }, new NpcWear { ShortName = "metal.facemask", SkinID = 0 }, new NpcWear { ShortName = "pants", SkinID = 0 }, new NpcWear { ShortName = "roadsign.kilt", SkinID = 0 }, new NpcWear { ShortName = "roadsign.gloves", SkinID = 0 }, new NpcWear { ShortName = "shoes.boots", SkinID = 0 }, }, BeltItems = new List { new NpcBelt { ShortName = "rifle.bolt", Amount = 1, SkinID = 0, Mods = new List { "weapon.mod.small.scope", "laserlight" }, Ammo = string.Empty }, new NpcBelt { ShortName = "syringe.medical", Amount = 10, SkinID = 0, Mods = new List(), Ammo = string.Empty }, } }); npcData.NPCs.Add(new NpcConfig() { ID = 202, Names = new List { "L96 header" }, Health = 200f, RoamRange = 30f, ChaseRange = 100f, AttackRangeMultiplier = 1f, SenseRange = 80f, MemoryDuration = 10f, DamageScale = 2.0f, AimConeScale = 1f, CheckVisionCone = false, VisionCone = 135f, Speed = 7.5f, DisableRadio = false, Stationary = false, IsRemoveCorpse = true, WearItems = new List { new NpcWear { ShortName = "metal.plate.torso", SkinID = 0 }, new NpcWear { ShortName = "hoodie", SkinID = 0 }, new NpcWear { ShortName = "metal.facemask", SkinID = 0 }, new NpcWear { ShortName = "pants", SkinID = 0 }, new NpcWear { ShortName = "roadsign.kilt", SkinID = 0 }, new NpcWear { ShortName = "roadsign.gloves", SkinID = 0 }, new NpcWear { ShortName = "shoes.boots", SkinID = 0 }, }, BeltItems = new List { new NpcBelt { ShortName = "rifle.l96", Amount = 1, SkinID = 0, Mods = new List { "weapon.mod.small.scope", "laserlight" }, Ammo = string.Empty }, new NpcBelt { ShortName = "syringe.medical", Amount = 10, SkinID = 0, Mods = new List(), Ammo = string.Empty }, } }); //shotguns npcData.NPCs.Add(new NpcConfig() { ID = 301, Names = new List { "Pump ur Ass" }, Health = 200f, RoamRange = 30f, ChaseRange = 100f, AttackRangeMultiplier = 1f, SenseRange = 80f, MemoryDuration = 10f, DamageScale = 2.0f, AimConeScale = 1f, CheckVisionCone = false, VisionCone = 135f, Speed = 7.5f, DisableRadio = false, Stationary = false, IsRemoveCorpse = true, WearItems = new List { new NpcWear { ShortName = "metal.plate.torso", SkinID = 0 }, new NpcWear { ShortName = "hoodie", SkinID = 0 }, new NpcWear { ShortName = "metal.facemask", SkinID = 0 }, new NpcWear { ShortName = "pants", SkinID = 0 }, new NpcWear { ShortName = "roadsign.kilt", SkinID = 0 }, new NpcWear { ShortName = "roadsign.gloves", SkinID = 0 }, new NpcWear { ShortName = "shoes.boots", SkinID = 0 }, }, BeltItems = new List { new NpcBelt { ShortName = "shotgun.pump", Amount = 1, SkinID = 0, Mods = new List { "weapon.mod.flashlight" }, Ammo = string.Empty }, new NpcBelt { ShortName = "syringe.medical", Amount = 10, SkinID = 0, Mods = new List(), Ammo = string.Empty }, } }); npcData.NPCs.Add(new NpcConfig() { ID = 302, Names = new List { "Spa ur Ass" }, Health = 200f, RoamRange = 30f, ChaseRange = 100f, AttackRangeMultiplier = 1f, SenseRange = 80f, MemoryDuration = 10f, DamageScale = 2.0f, AimConeScale = 1f, CheckVisionCone = false, VisionCone = 135f, Speed = 7.5f, DisableRadio = false, Stationary = false, IsRemoveCorpse = true, WearItems = new List { new NpcWear { ShortName = "metal.plate.torso", SkinID = 0 }, new NpcWear { ShortName = "hoodie", SkinID = 0 }, new NpcWear { ShortName = "metal.facemask", SkinID = 0 }, new NpcWear { ShortName = "pants", SkinID = 0 }, new NpcWear { ShortName = "roadsign.kilt", SkinID = 0 }, new NpcWear { ShortName = "roadsign.gloves", SkinID = 0 }, new NpcWear { ShortName = "shoes.boots", SkinID = 0 }, }, BeltItems = new List { new NpcBelt { ShortName = "shotgun.spas12", Amount = 1, SkinID = 0, Mods = new List { "weapon.mod.flashlight" }, Ammo = string.Empty }, new NpcBelt { ShortName = "syringe.medical", Amount = 10, SkinID = 0, Mods = new List(), Ammo = string.Empty }, } }); //smg npcData.NPCs.Add(new NpcConfig() { ID = 401, Names = new List { "MP ur mom" }, Health = 200f, RoamRange = 30f, ChaseRange = 100f, AttackRangeMultiplier = 1f, SenseRange = 80f, MemoryDuration = 10f, DamageScale = 2.0f, AimConeScale = 1f, CheckVisionCone = false, VisionCone = 135f, Speed = 7.5f, DisableRadio = false, Stationary = false, IsRemoveCorpse = true, WearItems = new List { new NpcWear { ShortName = "metal.plate.torso", SkinID = 0 }, new NpcWear { ShortName = "hoodie", SkinID = 0 }, new NpcWear { ShortName = "metal.facemask", SkinID = 0 }, new NpcWear { ShortName = "pants", SkinID = 0 }, new NpcWear { ShortName = "roadsign.kilt", SkinID = 0 }, new NpcWear { ShortName = "roadsign.gloves", SkinID = 0 }, new NpcWear { ShortName = "shoes.boots", SkinID = 0 }, }, BeltItems = new List { new NpcBelt { ShortName = "smg.mp5", Amount = 1, SkinID = 0, Mods = new List { "weapon.mod.holosight", "weapon.mod.flashlight" }, Ammo = string.Empty }, new NpcBelt { ShortName = "syringe.medical", Amount = 10, SkinID = 0, Mods = new List(), Ammo = string.Empty }, new NpcBelt { ShortName = "grenade.f1", Amount = 10, SkinID = 0, Mods = new List(), Ammo = string.Empty }, } }); npcData.NPCs.Add(new NpcConfig() { ID = 402, Names = new List { "Thom ur mom" }, Health = 200f, RoamRange = 30f, ChaseRange = 100f, AttackRangeMultiplier = 1f, SenseRange = 80f, MemoryDuration = 10f, DamageScale = 2.0f, AimConeScale = 1f, CheckVisionCone = false, VisionCone = 135f, Speed = 7.5f, DisableRadio = false, Stationary = false, IsRemoveCorpse = true, WearItems = new List { new NpcWear { ShortName = "metal.plate.torso", SkinID = 0 }, new NpcWear { ShortName = "hoodie", SkinID = 0 }, new NpcWear { ShortName = "metal.facemask", SkinID = 0 }, new NpcWear { ShortName = "pants", SkinID = 0 }, new NpcWear { ShortName = "roadsign.kilt", SkinID = 0 }, new NpcWear { ShortName = "roadsign.gloves", SkinID = 0 }, new NpcWear { ShortName = "shoes.boots", SkinID = 0 }, }, BeltItems = new List { new NpcBelt { ShortName = "smg.thompson", Amount = 1, SkinID = 0, Mods = new List { "weapon.mod.holosight", "weapon.mod.flashlight" }, Ammo = string.Empty }, new NpcBelt { ShortName = "syringe.medical", Amount = 10, SkinID = 0, Mods = new List(), Ammo = string.Empty }, new NpcBelt { ShortName = "grenade.f1", Amount = 10, SkinID = 0, Mods = new List(), Ammo = string.Empty }, } }); SaveNPCData(); } private JObject GetObjectConfig(NpcConfig config) { HashSet states = new HashSet { "RoamState", "ChaseState", "CombatState" }; return new JObject { ["Name"] = config.Names.GetRandom(), ["WearItems"] = new JArray { config.WearItems.Select(x => new JObject { ["ShortName"] = x.ShortName, ["SkinID"] = x.SkinID }) }, ["BeltItems"] = new JArray { config.BeltItems.Select(x => new JObject { ["ShortName"] = x.ShortName, ["Amount"] = x.Amount, ["SkinID"] = x.SkinID, ["Mods"] = new JArray { x.Mods }, ["Ammo"] = x.Ammo }) }, ["Kit"] = config.Kits.GetRandom(), ["Health"] = config.Health, ["RoamRange"] = config.RoamRange, ["ChaseRange"] = config.ChaseRange, ["SenseRange"] = config.SenseRange, ["ListenRange"] = config.SenseRange / 2f, ["AttackRangeMultiplier"] = config.AttackRangeMultiplier, ["CheckVisionCone"] = config.CheckVisionCone, ["VisionCone"] = config.VisionCone, ["HostileTargetsOnly"] = false, ["DamageScale"] = config.DamageScale, ["TurretDamageScale"] = 1f, ["AimConeScale"] = config.AimConeScale, ["DisableRadio"] = config.DisableRadio, ["Speed"] = config.Speed, ["AreaMask"] = 1, ["AgentTypeID"] = -1372625422, ["CanRunAwayWater"] = true, ["HomePosition"] = string.Empty, ["MemoryDuration"] = config.MemoryDuration, ["States"] = new JArray { states } }; } private NpcConfig GetNpcConfigByID(int id) { foreach (var npc in npcData.NPCs) { if (npc.ID == id) return npc; } return null; } #endregion #endregion #region Commands private void AddConvalanceCMDs() { AddCovalenceCommand("mestart", nameof(MEStartEventCMD)); AddCovalenceCommand("mestop", nameof(MEStopEventCMD)); AddCovalenceCommand("mestartrandom", nameof(MEStartRandomEventCMD)); } private void MEStartEventCMD(IPlayer iplayer, string command, string[] args) { //BasePlayer player = iplayer.Object as BasePlayer; if (!iplayer.HasPermission(AdminPerm)) { iplayer.Message("You don't have permission to use this command."); return; } if (args.Length == 0) { iplayer.Message("Invalid CMD."); return; } if (args.Length >= 1) { string preset = args[0]; var mConfig = FindMonumentConfig(preset); var monument = FindMonumentByName(preset); if (preset == null || monument == null) { iplayer.Message("Preset config not found with that name."); return; } if (monumentEvents.ContainsKey(preset)) { iplayer.Message("Already event is running."); return; } MonumentEvent eventStart = new MonumentEvent(mConfig.MonumentID); eventStart.StartEvent(); iplayer.Message(monument.displayPhrase.english + " event starting..."); return; } } private void MEStopEventCMD(IPlayer iplayer, string command, string[] args) { //BasePlayer player = iplayer.Object as BasePlayer; if (!iplayer.HasPermission(AdminPerm)) { iplayer.Message("You don't have permission to use this command."); return; } if (args.Length == 0) { iplayer.Message("Invalid CMD."); return; } if (args.Length >= 1) { string preset = args[0]; var mConfig = FindMonumentConfig(preset); var monument = FindMonumentByName(preset); if (preset == null || monument == null) { iplayer.Message("Preset config not found with that name."); return; } if (!monumentEvents.ContainsKey(preset)) { iplayer.Message( "event isn't running..."); return; } MonumentEvent eventStart = monumentEvents[mConfig.MonumentID]; eventStart.StopEvent(); iplayer.Message(monument.displayPhrase.english + " event stopped forced..."); return; } } private void MEStartRandomEventCMD(IPlayer iplayer, string command, string[] args) { //BasePlayer player = iplayer.Object as BasePlayer; if (!iplayer.HasPermission(AdminPerm)) { iplayer.Message("You don't have permission to use this command."); return; } if (args.Length == 0) { StartRandomFromPresets(); iplayer.Message("Started a random event."); return; } } [ChatCommand("meadd")] private void MEAddCMD(BasePlayer player, string cmd, string[] args) { if (!permission.UserHasPermission(player.UserIDString, AdminPerm)) { CM(player, "You don't have permission to use this command.") ; return; } if (args.Length == 0) { CM(player, "HELP COMMANDS\n- /meadd edit PRESET_NAME : Select a monument for editing spawnpoints.\n- /meadd npcspawn : Creates NPC spawn points for selected monument.\n- /meadd wavespawn : Creates Wave Npc spawn points for selected monument\n- /meadd cratespawn : Creates Crates spawn points for selected monument\n- /meadd stopedit : Deselect the current editing monument."); return; } else if (args.Length >= 1) { string command = args[0]; if (command == "npcspawn") { if (!editingMonument.ContainsKey(player)) { CM(player, "You are not editing any monument, select a monument first."); return; } string preset = editingMonument[player]; if (string.IsNullOrEmpty(preset)) { CM(player, "Invalid Args(Get Good)"); return; } var mConfig = FindMonumentConfig(preset); var monument = FindMonumentByName(preset); if (preset == null || monument == null) { CM(player, "Preset not found with that name."); return; } var mtx = monument.transform.worldToLocalMatrix; Vector3 pos = mtx.MultiplyPoint(player.transform.position); mConfig.npcSetting.npcPositions.Add(pos.ToString()); SaveConfig(); CM(player, "Npc spawn point added to " + monument.displayPhrase.english); return; } else if (command == "cratespawn") { if (!editingMonument.ContainsKey(player)) { CM(player, "You are not editing any monument, select a monument first."); return; } string preset = editingMonument[player]; if (string.IsNullOrEmpty(preset)) { CM(player, "Invalid Args(Get Good)"); return; } var mConfig = FindMonumentConfig(preset); var monument = FindMonumentByName(preset); if (preset == null || monument == null) { CM(player, "Preset not found with that name."); return; } var mtx = monument.transform.worldToLocalMatrix; Vector3 pos = mtx.MultiplyPoint(player.transform.position); mConfig.crateSetting.hackableLocations.Add(pos.ToString()); SaveConfig(); CM(player, "Crate spawn point added to " + monument.displayPhrase.english); return; } else if (command == "wavespawn") { if (!editingMonument.ContainsKey(player)) { CM(player, "You are not editing any monument, select a monument first."); return; } string preset = editingMonument[player]; if (string.IsNullOrEmpty(preset)) { CM(player, "Invalid Args(Get Good)"); return; } var mConfig = FindMonumentConfig(preset); var monument = FindMonumentByName(preset); if (preset == null || monument == null) { CM(player, "Preset not found with that name."); return; } var mtx = monument.transform.worldToLocalMatrix; Vector3 pos = mtx.MultiplyPoint(player.transform.position); mConfig.waveSetting.npcDropLocations.Add(pos.ToString()); SaveConfig(); CM(player, "Wave Npc spawn point added to " + monument.displayPhrase.english); return; } else if (command == "edit") { string preset = args[1]; if (string.IsNullOrEmpty(preset)) { CM(player, "Invalid Args(Get Good)"); return; } var mConfig = FindMonumentConfig(preset); var monument = FindMonumentByName(preset); if (preset == null) { CM(player, "Preset not found with that name."); return; } if (monument == null) { CM(player, "Monument not found with that name."); return; } if (editingMonument.ContainsKey(player)) { editingMonument.Remove(player); } editingMonument.Add(player, preset); showSpawnPoints = timer.Every(1, () => { foreach (var c in mConfig.npcSetting.npcPositions) { Vector3 pos = GetLocalToWorldMatrix(monument.transform, c); player.SendConsoleCommand("ddraw.sphere", 1, Color.green, pos, 1f); player.SendConsoleCommand("ddraw.text", 1, Color.yellow, pos, $"Npc Spawn Point\nPosition: {c}"); } foreach (var c in mConfig.crateSetting.hackableLocations) { Vector3 pos = GetLocalToWorldMatrix(monument.transform, c); player.SendConsoleCommand("ddraw.sphere", 1, Color.green, pos, 1f); player.SendConsoleCommand("ddraw.text", 1, Color.yellow, pos, $"Crate Spawn Point\nPosition: {c}"); } foreach (var c in mConfig.waveSetting.npcDropLocations) { Vector3 pos = GetLocalToWorldMatrix(monument.transform, c); player.SendConsoleCommand("ddraw.sphere", 1, Color.green, pos, 1f); player.SendConsoleCommand("ddraw.text", 1, Color.yellow, pos, $"Wave Npc Spawn Point\nPosition: {c}"); } }); CM(player, "Showing all spawnpoints for " + monument.displayPhrase.english + "\nHide them with /meadd stopedit ..."); return; } else if (command == "stopedit") { if (!editingMonument.ContainsKey(player)) { CM(player, "You are not editing any monument, select a monument first."); return; } string preset = editingMonument[player]; if (string.IsNullOrEmpty(preset)) { CM(player, "Invalid Args(Get Good)"); return; } var mConfig = FindMonumentConfig(preset); var monument = FindMonumentByName(preset); if (preset == null || monument == null) { CM(player, "Preset not found with that name."); return; } if (showSpawnPoints != null) { showSpawnPoints.Destroy(); } CM(player, "Stoped editing spawnpoints for " + monument.displayPhrase.english); editingMonument.Remove(player); return; } } } [ChatCommand("me_test")] private void TestMETest(BasePlayer player, string command, string[] args) { //StopAllEvent(); } #endregion #region Loads private void Init() { //Puts("MonumentEvents Loading..."); } private void OnServerInitialized() { if (!plugins.Exists("NpcSpawn")) { PrintError("NpcSpawn plugin is Required! Get it from - https://codefling.com/extensions/npc-spawn"); NextTick(() => Interface.Oxide.UnloadPlugin(Name)); return; } ins = this; //datas LoadSettingsData(); LoadNPCData(); AddConvalanceCMDs(); RegisterPermissions(); LoadImages(); //Auto Start AutoEventStart(); } private void Unload() { StopAllEvent(); if (autoCoroutine != null) { ServerMgr.Instance.StopCoroutine(autoCoroutine); autoCoroutine = null; } } private void OnServerSave() { SaveNPCData(); SaveSettingsData(); } #endregion #region MainEvent private void AutoEventStart() { if (!config.scheduledEvents.scheduledEvent) return; if (autoCoroutine != null) return; autoCoroutine = ServerMgr.Instance.StartCoroutine(AutoEventCorountine()); } private IEnumerator AutoEventCorountine() { if (!config.scheduledEvents.scheduledEvent) yield break; int time = Oxide.Core.Random.Range(config.scheduledEvents.minTime, config.scheduledEvents.maxTime); PrintWarning("Next event in " + FormatTime(time) + " seconds."); yield return CoroutineEx.waitForSeconds(time); // Check if the number of active events is below the maximum allowed // and if the number of active players is equal to or greater than the minimum required players if ((monumentEvents.Count < config.scheduledEvents.maxEventsServer) && (BasePlayer.activePlayerList.Count >= config.scheduledEvents.minimiumRequiredplayer)) { StartRandomFromPresets(); // Start a random event from the presets } else { PrintWarning("Event not started, conditions not met."); autoCoroutine = null; autoCoroutine = ServerMgr.Instance.StartCoroutine(AutoEventCorountine()); } } private void StartRandomFromPresets() { List list; if (config.scheduledEvents.spawnFromList) { list = config.scheduledEvents.scheduledList .Select(entry => FindMonumentConfig(entry)) .Where(e => e != null) .ToList(); } else { list = settings.monumentSettings .Where(x => x.enableMonument) .ToList(); } list = list .Where(x => !monumentEvents.ContainsKey(x.MonumentID) && (FindMonumentByName(x.MonumentID) != null)) .ToList(); if (list.Count == 0) { autoCoroutine = null; if (config.scheduledEvents.scheduledEvent) { autoCoroutine = ServerMgr.Instance.StartCoroutine(AutoEventCorountine()); } return; } int randomIndex = UnityEngine.Random.Range(0, list.Count); MonumentEvent eventStart = new MonumentEvent(list[randomIndex].MonumentID); eventStart.StartEvent(); autoCoroutine = null; if (config.scheduledEvents.scheduledEvent) { autoCoroutine = ServerMgr.Instance.StartCoroutine(AutoEventCorountine()); } } private void StopAllEvent() { if (monumentEvents.Count < 1) return; // Create a list to store keys of events that need to be stopped List eventsToStop = new List(); // Iterate over the monumentEvents in reverse order to safely remove items foreach (var kvp in monumentEvents.Reverse()) { MonumentEvent startedEvent = kvp.Value; if (startedEvent != null) { startedEvent.StopEvent(); eventsToStop.Add(kvp.Key); // Store the key of the event to stop } } // Remove the stopped events from the monumentEvents dictionary foreach (var eventId in eventsToStop) { monumentEvents.Remove(eventId); } } private MonumentEvent FindEvent(string Name) { foreach(var i in monumentEvents) { if (i.Key == Name) return i.Value; } return null; } private class MonumentEvent { string EventID; public MonumentInfo monumentInfo; public SavedSettings.MonumentSetting monumentConfig; private MilitaryCH47Crate ch47Crate; private MilitaryCH47NPCs ch47Npcs; private GameObject gameObject; Timer eventTimer; Timer crateTimer; Timer patrolTimer; public int time; bool waveStarted = false; Dictionary> sphereEntities = new Dictionary>(); Dictionary> mapMarkers = new Dictionary>(); public MapMarkerGenericRadius mapMarkerGenericRadius; public List hackableLockedCrates = new List(); public HashSet scientistNpcs = new HashSet(); public List patrolHelis = new List(); public string ZonesActive; public HashSet PlayersIN = new HashSet(); public Hash playersMapNotes = new Hash(); public string description = string.Empty; public MonumentEvent(string Name) { EventID = Name; monumentInfo = FindMonumentByName(Name); monumentConfig = FindMonumentConfig(Name); } public void StartEvent() { time = ins.config.notificationTimer; ins.monumentEvents.Add(monumentConfig.MonumentID, this); //wait for time ins.SendMessage("EventPreNotify", this); eventTimer = ins.timer.In(ins.config.notificationTimer, () => { //send announcement ins.SendMessage("EventStartMSG", this); //ui description = ins.GetLang("EventStartMSG").Replace("{EVENT_NAME}", monumentInfo.displayPhrase.english); //Create Sphere Dome CreateDome(); //create zone CreatePVPZone(); //Create Map Markers foreach (var player in BasePlayer.activePlayerList) AddMapNote(player); CreateMapMarker(); //Reset Puzzle PuzzleReset(); //Spawn Crates SpawnCrates(); //Spawn Npcs try { CreateScientist(); } catch { } //spawn Patrol Heli CallHeliAIandStrafe(); CreateTrigger(); //Timer start StartTimer(); Interface.CallHook("OnMEStarted", monumentConfig.MonumentID); }); } public void StopEvent() { eventTimer.Destroy(); try { //destroy Sphere Dome DestroyDomes(); DestoryTrigger(); //eraze zone ErazeZone(); //destroy Map Markers DestroyMapMarkers(); //destroy crates DestoryCrates(); //destroy npcs KillScientistNpcs(); //kill patrols immidiate KillsPatrolHelis(); RemoveWave(); DestoryMapNote(); ClearData(); } catch { } //send announcement ins.SendMessage("EventEndMSG", this); Interface.CallHook("OnMEStop", monumentConfig.MonumentID); } private void StartTimer() { time = monumentConfig.timer; int timeInterval = (ins.config.intervalTmer != 0) ? ins.config.intervalTmer : 1; eventTimer = ins.timer.Repeat(timeInterval, time, () => { time -= timeInterval; foreach (var player in PlayersIN) { try { PingHackableCrateFor(player); } catch { } } switch (time) { case 300: ins.SendMessage("EventEndingSoon", this); // description = ins.GetLang("EventEndingSoon").Replace("{EVENT_NAME}", monumentInfo.displayPhrase.english).Replace("{TIMER}", ins.FormatTime(time)); return; case 0: StopEvent(); return; } }); } //works private void CreateTrigger() { gameObject = new GameObject(); gameObject.transform.position = monumentInfo.transform.position; gameObject.layer = (int)Rust.Layer.Reserved1; SphereCollider collider = gameObject.AddComponent(); collider.radius = monumentConfig.domeSetting.sphereRadius / 2; collider.isTrigger = true; MonumentTrigger trigger = gameObject.AddComponent(); trigger.monumentEvent = this; gameObject.AddComponent().InterestLayers = (int)Rust.Layer.Player_Server; } private void DestoryTrigger() { UnityEngine.Object.Destroy(gameObject); } private void CreatePVPZone() { if (!monumentConfig.pveSetting.enablePVPSupport || !ins.ZoneManager) return; string[] messages = new string[4]; messages[0] = "name"; messages[1] = EventID; messages[2] = "radius"; messages[3] = Convert.ToInt32(monumentConfig.domeSetting.sphereRadius).ToString(); ins.ZoneManager?.CallHook("CreateOrUpdateZone", EventID, messages, monumentInfo.transform.position); ZonesActive = EventID; } public void ErazeZone() { if (!monumentConfig.pveSetting.enablePVPSupport || !ins.ZoneManager || string.IsNullOrEmpty(ZonesActive)) return; ins.ZoneManager?.CallHook("EraseZone", ZonesActive); } private void CreateDome() { if (!monumentConfig.domeSetting.enableSphere) return; if (monumentConfig.domeSetting.sphereAmount > 0) { sphereEntities.Add(monumentInfo, new List()); for (int i = 0; i < monumentConfig.domeSetting.sphereAmount; i++) { BaseEntity entity = GameManager.server.CreateEntity("assets/bundled/prefabs/modding/events/twitch/br_sphere_red.prefab", monumentInfo.transform.position, new Quaternion(), true); SphereEntity bubblee = entity.GetComponent(); bubblee.currentRadius = monumentConfig.domeSetting.sphereRadius; bubblee.lerpSpeed = 0f; entity.Spawn(); BaseEntity ent = GameManager.server.CreateEntity("assets/prefabs/visualization/sphere.prefab", monumentInfo.transform.position, new Quaternion(), true); SphereEntity bubble = ent.GetComponent(); bubble.currentRadius = monumentConfig.domeSetting.sphereRadius; bubble.lerpSpeed = 0f; ent.Spawn(); sphereEntities[monumentInfo].Add(bubblee); sphereEntities[monumentInfo].Add(bubble); } } } private void DestroyDomes() { if (sphereEntities.ContainsKey(monumentInfo)) { List domeEntities = sphereEntities[monumentInfo]; foreach (SphereEntity domeEntity in domeEntities) { if (domeEntity != null && !domeEntity.IsDestroyed) { domeEntity.Kill(); } } sphereEntities.Remove(monumentInfo); } } private void CreateMapMarker() { if (!monumentConfig.mapMarker.enableMarker) return; /*VendingMachineMapMarker vending = GameManager.server.CreateEntity("assets/prefabs/deployable/vendingmachine/vending_mapmarker.prefab", monumentInfo.transform.position).GetComponent(); vending.enableSaving = false; vending.markerShopName = monumentConfig.mapMarker.markerText.Replace("{TIMER}", ins.FormatTime(time)); vending.name = monumentConfig.mapMarker.markerText.Replace("{TIMER}", ins.FormatTime(time)); vending.Spawn();*/ mapMarkerGenericRadius = GameManager.server.CreateEntity("assets/prefabs/tools/map/genericradiusmarker.prefab").GetComponent(); mapMarkerGenericRadius.color1 = HexToUnityColor(monumentConfig.mapMarker.markerColor); mapMarkerGenericRadius.color2 = HexToUnityColor(monumentConfig.mapMarker.markerColorOutline); mapMarkerGenericRadius.alpha = monumentConfig.mapMarker.markerTransparency; mapMarkerGenericRadius.radius = monumentConfig.mapMarker.markerRadius; mapMarkerGenericRadius.enableSaving = false; mapMarkerGenericRadius.transform.position = monumentInfo.transform.position; mapMarkerGenericRadius.Spawn(); //vending.SendNetworkUpdate(); mapMarkerGenericRadius.SendUpdate(); // Add the map markers to the dictionary // mapMarkers.Add(monumentInfo, new Tuple(vending, generic)); } private void DestroyMapMarkers() { // Check if the monumentInfo exists in the mapMarkers dictionary /*if (mapMarkers.ContainsKey(monumentInfo)) { Tuple markers = mapMarkers[monumentInfo]; // Destroy the map markers if (markers.Item1 != null && !markers.Item1.IsDestroyed) { markers.Item1.Kill(); } if (markers.Item2 != null && !markers.Item2.IsDestroyed) { markers.Item2.Kill(); } markers.Item1.SendNetworkUpdate(); markers.Item2.SendUpdate(); // Remove the entry for monumentInfo from the mapMarkers dictionary mapMarkers.Remove(monumentInfo); }*/ if (mapMarkerGenericRadius != null && !mapMarkerGenericRadius.IsDestroyed) { mapMarkerGenericRadius.Kill(); } } private void UpdateGenericMapMarker() { if (mapMarkerGenericRadius && !mapMarkerGenericRadius.IsDestroyed) mapMarkerGenericRadius.SendUpdate(); } private void SendMarkerUpdate() { if (mapMarkers.ContainsKey(monumentInfo)) { Tuple markers = mapMarkers[monumentInfo]; // Update the map markers if (markers.Item1 != null && !markers.Item1.IsDestroyed && markers.Item2 != null && !markers.Item2.IsDestroyed) { //markers.Item1.markerShopName = monumentConfig.mapMarker.markerText.Replace("{TIMER}", ins.FormatTime(time)); //markers.Item1.name = monumentConfig.mapMarker.markerText.Replace("{TIMER}", ins.FormatTime(time)); markers.Item1.SendNetworkUpdate(); markers.Item2.SendUpdate(); } } } private void PuzzleReset() { if (!monumentConfig.resetPuzzle) return; PuzzleReset[] puzzleResets = UnityEngine.Object.FindObjectsOfType(); for (int i = 0; i < puzzleResets.Length; i++) { PuzzleReset puzzle = puzzleResets[i]; if (Vector3.Distance(monumentInfo.transform.position, puzzle.transform.position) <= monumentConfig.domeSetting.sphereRadius) { puzzle.DoReset(); puzzle.ResetTimer(); } } } private void SpawnCrates() { if (!monumentConfig.crateSetting.spawnCrates) return; if (monumentConfig.crateSetting.hackableAmount > 0 && monumentConfig.crateSetting.hackableLocations.Count > 0) { crateTimer = ins.timer.In(30, () => { //CallChinok chin = CallChinok.CreateCh47(monumentInfo, monumentConfig, this, GetRandomFromMono(monumentInfo.transform, monumentConfig.crateSetting.hackableLocations).GetRandom()); //chinokCrates.Add(chin); var position = GetRandomFromMono(monumentInfo.transform, monumentConfig.crateSetting.hackableLocations).GetRandom(); CH47Helicopter cH47Helicopter = ins.CreateEntity("assets/prefabs/npc/ch47/ch47scientists.entity.prefab", ins.GetRandomVectorAroundPoint(position, 400, 600)); cH47Helicopter.Spawn(); ch47Crate = cH47Helicopter.gameObject.AddComponent(); ch47Crate.Initialize(position, this); }); } } private void DestoryCrates() { if (crateTimer != null) crateTimer.Destroy(); foreach (var ent in hackableLockedCrates) { if (ent.IsValid()) ent.Kill(); } if (ch47Crate != null) ch47Crate.Kill(); if (ch47Npcs != null) ch47Npcs.Kill(); } private void CreateScientist() { if (monumentConfig == null || monumentConfig.npcSetting == null) { ins.PrintWarning("Monument config or NPC settings are null."); return; } if (!monumentConfig.npcSetting.spawnNpc) return; if (monumentConfig.npcSetting.npcAmount > 0 && monumentConfig.npcSetting.npcPositions.Count > 0) { int positionIndex = 0; // Start with the first position in the list for (int i = 0; i < monumentConfig.npcSetting.npcAmount; i++) { try { if (monumentConfig.npcSetting.npcIDs == null || monumentConfig.npcSetting.npcIDs.Count == 0) { ins.PrintWarning("NPC IDs list is null or empty."); return; } NpcConfig npcConfig = ins.GetNpcConfigByID(monumentConfig.npcSetting.npcIDs.GetRandom()); if (npcConfig == null) { ins.PrintWarning("Failed to get a valid NPC config."); continue; } Vector3 randomPosition = GetLocalToWorldMatrix(monumentInfo.transform, monumentConfig.npcSetting.npcPositions[positionIndex]); // Increment the position index for the next iteration, looping back to the start if needed positionIndex = (positionIndex + 1) % monumentConfig.npcSetting.npcPositions.Count; if (!IsScientistNPCAtPosition(randomPosition)) { if (ins.NpcSpawn == null) { ins.PrintWarning("NpcSpawn is null."); return; } ScientistNPC npc = (ScientistNPC)ins.NpcSpawn.Call("SpawnNpc", randomPosition, ins.GetObjectConfig(npcConfig)); if (npc == null) { ins.PrintWarning("Failed to spawn ScientistNPC."); continue; } scientistNpcs.Add(npc); } } catch { } } } } private void KillScientistNpcs() { if (scientistNpcs.Count < 1) return; // Create a copy of the list to avoid modifying it during iteration List npcListCopy = new List(scientistNpcs); foreach (var activeScientistNpc in npcListCopy) { if (activeScientistNpc.IsValid()) { activeScientistNpc.Kill(); scientistNpcs.Remove(activeScientistNpc); } } } private void CallHeliAIandStrafe() { if (!monumentConfig.patrolSetting.spawnPaHeli) return; if (monumentConfig.patrolSetting.patrolHeliAmount > 0) { patrolTimer = ins.timer.In(120, () => { for (int i = 0; i < monumentConfig.patrolSetting.patrolHeliAmount; i++) { PatrolHeliRoam heliRoam = PatrolHeliRoam.CreateHelicopter(monumentInfo, monumentInfo.transform.position); patrolHelis.Add(heliRoam); } ins.SendMessage("EventPatrolHeliCover", this); }); } } private void KillsPatrolHelis() { if (patrolTimer != null) patrolTimer.Destroy(); if (patrolHelis.Count < 1) return; foreach (var ent in patrolHelis) { ent.KillHeli(); } } private void ClearData() { ins.monumentEvents.Remove(monumentConfig.MonumentID); } public void SendWave() { if (!monumentConfig.waveSetting.enableWave) return; if (waveStarted) return; ins.SendMessage("EventBackupSent", this); description = ins.GetLang("EventBackupSent").Replace("{EVENT_NAME}", monumentInfo.displayPhrase.english); waveStarted = true; if (monumentConfig.waveSetting.npcAmount > 0) { Vector3 position = GetRandomFromMono(monumentInfo.transform, monumentConfig.waveSetting.npcDropLocations).GetRandom(); CH47Helicopter cH47Helicopter = ins.CreateEntity("assets/prefabs/npc/ch47/ch47scientists.entity.prefab", ins.GetRandomVectorAroundPoint(position, 400, 600)); cH47Helicopter.Spawn(); ch47Npcs = cH47Helicopter.gameObject.AddComponent(); ch47Npcs.Initialize(position, this); } if (monumentConfig.waveSetting.patrolAmount > 0) { PatrolHeliRoam heliRoam = PatrolHeliRoam.CreateHelicopter(monumentInfo, monumentInfo.transform.position); patrolHelis.Add(heliRoam); } } private void RemoveWave() { if (ch47Npcs != null) ch47Npcs.Kill(); } private void DestoryMapNote() { foreach (var player in BasePlayer.activePlayerList) RemoveMapNote(player); } public void AddMapNote(BasePlayer player) { if (!monumentConfig.MapNote.Enabled) return; if (playersMapNotes.ContainsKey(player.userID)) return; MapNote mapNote = new MapNote() { icon = monumentConfig.MapNote.IconIndex, colourIndex = monumentConfig.MapNote.IconColor, isPing = monumentConfig.MapNote.IconType == 1, label = monumentConfig.MapNote.Text, noteType = 1, worldPosition = monumentInfo.transform.position, ShouldPool = true }; if (player.State.pointsOfInterest == null) player.State.pointsOfInterest = Pool.Get>(); player.State.pointsOfInterest.Add(mapNote); player.DirtyPlayerState(); player.SendMarkersToClient(); playersMapNotes.Add(player.userID, mapNote); } public void RemoveMapNote(BasePlayer player) { if (!playersMapNotes.ContainsKey(player.userID)) return; MapNote mapNote = playersMapNotes[player.userID]; mapNote.Dispose(); player.State.pointsOfInterest.Remove(mapNote); player.DirtyPlayerState(); player.SendMarkersToClient(); playersMapNotes.Remove(player.userID); } public object OnMapMarkerRemove(BasePlayer player, MapNote note) { if (!playersMapNotes.ContainsKey(player.userID)) return null; if (playersMapNotes[player.userID] == note) return true; return null; } public void OnPlayerConnected(BasePlayer player) { AddMapNote(player); UpdateGenericMapMarker(); } public void OnPlayerDisconnected(BasePlayer player) { RemoveMapNote(player); } public void OnPlayerDeath(BasePlayer player, HitInfo hit) { if (PlayersIN.Contains(player)) { PlayersIN.Remove(player); } } private void PingHackableCrateFor(BasePlayer player) { if (hackableLockedCrates.Count == 0) return; bool isTempAdmin = false; try { if (!player.IsAdmin) { player.SetPlayerFlag(BasePlayer.PlayerFlags.IsAdmin, true); player.SendNetworkUpdateImmediate(); isTempAdmin = true; } player.SendConsoleCommand("ddraw.text", new object[] { 1f, Color.red, hackableLockedCrates.First().transform.position + Vector3.up * 2f, $"◈" }); } finally { if (isTempAdmin) { player.SetPlayerFlag(BasePlayer.PlayerFlags.IsAdmin, false); player.SendNetworkUpdateImmediate(); } } } //Methods public MonumentInfo FindMonumentByName(string name) { List monuments = new List();//UnityEngine.Object.FindObjectsOfType(); foreach (MonumentInfo monument in TerrainMeta.Path.Monuments) { if (monument.name.Contains(name)) { monuments.Add(monument); } } // If there are no matching monuments, return null if (monuments.Count == 0) { return null; } // Pick a random monument from the list int randomIndex = Oxide.Core.Random.Range(0, monuments.Count); return monuments[randomIndex]; } private SavedSettings.MonumentSetting FindMonumentConfig(string name) { foreach (SavedSettings.MonumentSetting mon in ins.settings.monumentSettings) { if (mon.MonumentID == name) return mon; } return null; } private Color HexToUnityColor(string hex) { Color unityColor = Color.white; // Default color in case of an invalid hex value if (ColorUtility.TryParseHtmlString(hex, out unityColor)) { return unityColor; } Debug.LogError("Invalid hex color value: " + hex); return unityColor; } private static Vector3 GetLocalToWorldMatrix(Transform transform, string location) { string[] components = location.Replace("(", "").Replace(")", "").Split(','); if (components.Length != 3) { Debug.LogError($"Invalid format for location: {location}"); return Vector3.zero; } if (!float.TryParse(components[0], out float x) || !float.TryParse(components[1], out float y) || !float.TryParse(components[2], out float z)) { Debug.LogError($"Failed to parse components for location: {location}"); return Vector3.zero; } Vector3 start = new Vector3(x, y, z); var mtx = transform.localToWorldMatrix; Vector3 position = mtx.MultiplyPoint(start); return position; } public static List GetRandomFromMono(Transform transform, List locations) { List result = new List(); foreach (var g1 in locations) { string[] components = g1.Replace("(", "").Replace(")", "").Split(','); // Ensure we have exactly three components if (components.Length != 3) { Debug.LogError($"Invalid format for location: {g1}"); continue; // Skip to the next location } // Attempt to parse components into floats if (!float.TryParse(components[0], out float x) || !float.TryParse(components[1], out float y) || !float.TryParse(components[2], out float z)) { Debug.LogError($"Failed to parse components for location: {g1}"); continue; // Skip to the next location } Vector3 start = new Vector3(x, y, z); var mtx = transform.localToWorldMatrix; Vector3 position = mtx.MultiplyPoint(start); result.Add(position); } return result; } private bool IsScientistNPCAtPosition(Vector3 position) { foreach (var npc in scientistNpcs) { if (npc == null || npc.IsDead()) continue; if (Vector3.Distance(npc.transform.position, position) < 0.1f) { return true; // NPC already exists at this position } } return false; // NPC does not exist at this position } private ScientistNPC FindNPCByNetID(NetworkableId netID) { foreach (var entity in BaseNetworkable.serverEntities) { if (entity is ScientistNPC npc && npc.net.ID == netID) { return npc; } } return null; } public bool IsEventNpc(ScientistNPC npc) { foreach(var entity in scientistNpcs) { if (entity.net.ID == npc.net.ID) return true; } return false; } //Behaviors } #endregion #region Methods private bool HasAdminPerm(IPlayer player) { if (permission.UserHasPermission(player.Id.ToString(), AdminPerm)) return true; return false; } private void RegisterPermissions() { if (!permission.PermissionExists(AdminPerm)) permission.RegisterPermission(AdminPerm, this); } private Vector3 SetPositionInRange(float minRange, float maxRange, float positionRange) { float randomX = Oxide.Core.Random.Range(-maxRange, maxRange); float randomZ = Oxide.Core.Random.Range(-maxRange, maxRange); // Ensure the random position is within the desired position range if (Mathf.Abs(randomX) < positionRange) { randomX = Mathf.Sign(randomX) * positionRange; } if (Mathf.Abs(randomZ) < positionRange) { randomZ = Mathf.Sign(randomZ) * positionRange; } return new Vector3(randomX, 255f, randomZ); } private void SendTipBroadcast(string message, float duration) { foreach(var player in BasePlayer.activePlayerList) { if (player != null && player.IsConnected) { player?.SendConsoleCommand("gametip.showgametip", message); timer.Once(duration, () => { player?.SendConsoleCommand("gametip.hidegametip"); }); } } } private void SendMessage(string langMSG, MonumentEvent monumentEvent) { if (ins.config.chatSetting.type == "BOTH") { ins.Broadcast(ins.GetLang(langMSG).Replace("{EVENT_NAME}", monumentEvent.monumentInfo.displayPhrase.english).Replace("{TIMER}", ins.FormatTime(monumentEvent.time)).Replace("{NOTIFYTIME}", ins.FormatTime(monumentEvent.time))); ins.SendTipBroadcast(ins.GetLang(langMSG).Replace("{EVENT_NAME}", monumentEvent.monumentInfo.displayPhrase.english).Replace("{TIMER}", ins.FormatTime(monumentEvent.time)).Replace("{NOTIFYTIME}", ins.FormatTime(monumentEvent.time)), 8); } else if (ins.config.chatSetting.type == "CHAT") { ins.Broadcast(ins.GetLang(langMSG).Replace("{EVENT_NAME}", monumentEvent.monumentInfo.displayPhrase.english).Replace("{TIMER}", ins.FormatTime(monumentEvent.time)).Replace("{NOTIFYTIME}", ins.FormatTime(monumentEvent.time))); } else if (ins.config.chatSetting.type == "TIP") { ins.SendTipBroadcast(ins.GetLang(langMSG).Replace("{EVENT_NAME}", monumentEvent.monumentInfo.displayPhrase.english).Replace("{TIMER}", ins.FormatTime(monumentEvent.time)).Replace("{NOTIFYTIME}", ins.FormatTime(monumentEvent.time)), 8); } else { ins.Puts("Invalid Message Type. Please use Type as BOTH or CHAT or TIP"); } } private object FindPointOnNavmesh(Vector3 targetPosition) { for (var i = 0; i < 20; i++) { targetPosition.y = GetSpawnHeight(targetPosition); if (!NavMesh.SamplePosition(targetPosition, out navmeshHit, 10f, 1)) continue; /*if (IsInRockPrefab(navmeshHit.position)) continue; if (IsNearWorldCollider(navmeshHit.position)) continue;*/ return navmeshHit.position; } return null; } private static float GetSpawnHeight(Vector3 target) { var y = TerrainMeta.HeightMap.GetHeight(target); var p = TerrainMeta.HighestPoint.y + 250f; if (UnityEngine.Physics.Raycast(new Vector3(target.x, p, target.z), Vector3.down, out raycastHit, target.y + p, Layers.Mask.World, QueryTriggerInteraction.Ignore)) y = Mathf.Max(y, raycastHit.point.y); return y; } private static bool IsInRockPrefab(Vector3 position) { UnityEngine.Physics.queriesHitBackfaces = true; var isInRock = UnityEngine.Physics.Raycast(position, Vector3.up, out raycastHit, 20f, WORLD_LAYER, QueryTriggerInteraction.Ignore) && BlockedColliders.Any(s => raycastHit.collider?.gameObject?.name.Contains(s, System.Globalization.CompareOptions.OrdinalIgnoreCase) ?? false); UnityEngine.Physics.queriesHitBackfaces = false; return isInRock; } private static bool IsNearWorldCollider(Vector3 position) { UnityEngine.Physics.queriesHitBackfaces = true; var count = UnityEngine.Physics.OverlapSphereNonAlloc(position, 2f, _buffer, WORLD_LAYER, QueryTriggerInteraction.Ignore); UnityEngine.Physics.queriesHitBackfaces = false; var removed = 0; for (var i = 0; i < count; i++) { if (AcceptedColliders.Any(s => _buffer[i].gameObject.name.Contains(s))) removed++; } return removed != count; } private Vector3 GetRandomVectorAroundPoint(Vector3 center, float minRadius, float maxRadius) { // Generate random angles in radians float angle = UnityEngine.Random.value * Mathf.PI * 2; // Calculate a random distance within the specified range float distance = UnityEngine.Random.Range(minRadius, maxRadius); // Calculate the X and Z coordinates of the random point float x = center.x + Mathf.Cos(angle) * distance; float z = center.z + Mathf.Sin(angle) * distance; // Keep the Y coordinate the same as the center float y = center.y + 100; // Create and return the random vector return new Vector3(x, y, z); } private Vector3 GetLocalToWorldMatrix(Transform transform, string location) { // Remove parentheses and split the string into components string[] components = location.Replace("(", "").Replace(")", "").Split(','); // Ensure we have exactly three components if (components.Length != 3) { Debug.LogError($"Invalid format for location: {location}"); return Vector3.zero; } // Attempt to parse components into floats if (!float.TryParse(components[0], out float x) || !float.TryParse(components[1], out float y) || !float.TryParse(components[2], out float z)) { Debug.LogError($"Failed to parse components for location: {location}"); return Vector3.zero; } // Create the Vector3 from parsed components Vector3 start = new Vector3(x, y, z); // Apply the transformation var mtx = transform.localToWorldMatrix; Vector3 position = mtx.MultiplyPoint(start); return position; } public List GetRandomFromMono(Transform transform, List locations) { List result = new List(); foreach (var g1 in locations) { string[] components = g1.Replace("(", "").Replace(")", "").Split(','); // Ensure we have exactly three components if (components.Length != 3) { Debug.LogError($"Invalid format for location: {g1}"); continue; // Skip to the next location } // Attempt to parse components into floats if (!float.TryParse(components[0], out float x) || !float.TryParse(components[1], out float y) || !float.TryParse(components[2], out float z)) { Debug.LogError($"Failed to parse components for location: {g1}"); continue; // Skip to the next location } Vector3 start = new Vector3(x, y, z); var mtx = transform.localToWorldMatrix; Vector3 position = mtx.MultiplyPoint(start); result.Add(position); } return result; } public MonumentInfo FindMonumentByName(string name) { List monuments = new List();//UnityEngine.Object.FindObjectsOfType(); foreach (MonumentInfo monument in TerrainMeta.Path.Monuments) { if (monument.name.Contains(name)) { monuments.Add(monument); } } // If there are no matching monuments, return null if (monuments.Count == 0) { return null; } // Pick a random monument from the list int randomIndex = Oxide.Core.Random.Range(0, monuments.Count); return monuments[randomIndex]; } private string FormatTime(double time) { TimeSpan dateDifference = TimeSpan.FromSeconds(time); int days = dateDifference.Days; int hours = dateDifference.Hours; int mins = dateDifference.Minutes; int secs = dateDifference.Seconds; if (days > 0) return $"{days:00}d:{hours:00}h"; if (hours > 0) return $"{hours:00}h:{mins:00}m"; return mins > 0 ? $"{mins:00}m:{secs:00}s" : $"{secs}s"; } private bool IsRoamHeli(PatrolHelicopter patrolHelicopter) { if (patrolHelicopter == null || patrolHelicopter.net == null || patrolHelicopter.net == null || monumentEvents.Count < 1 || monumentEvents != null) return false; foreach(var entity in monumentEvents) { var eve = entity.Value; if (eve.patrolHelis.Count > 0) { foreach (var e in eve.patrolHelis) { PatrolHeliRoam roam = e; if (roam.GetHeliNetId() == patrolHelicopter.net.ID.Value) return true; } } } return false; } private void Broadcast(string Message) { foreach(var player in BasePlayer.activePlayerList) { CM(player, Message); } } private void CM(BasePlayer player, string Message) { Player.Message(player, config.chatSetting.prefix + Message, config.chatSetting.avatarID); } private Dictionary Images = new Dictionary(); private void AddImage(string url) { if (string.IsNullOrEmpty(url)) return; if (!ImageLibrary.Call("HasImage", url)) ImageLibrary.Call("AddImage", url, url); timer.In(1f, () => { if (Images.ContainsKey(url)) Images.Remove(url); Images.Add(url, ImageLibrary.Call("GetImage", url)); }); } private void LoadImages() { if (ImageLibrary == null) { PrintError("[ImageLibrary] not found!"); return; } // AddImage("https://i.postimg.cc/W3mGVmQ4/alarm-clock.png"); } private string GetImage(string url) { return Images[url]; } #endregion #region UIs private void EventUI(BasePlayer player, string Title, string Description, int NPCs, int countdown) { var container = new CuiElementContainer(); container.Add(new CuiPanel { Image = { Color = "0 0 0 1", Material = "assets/content/ui/namefontmaterial.mat", Sprite = "assets/content/ui/ui.background.transparent.linearltr.tga" }, RectTransform = { AnchorMin = "0.5 0.5", AnchorMax = "0.5 0.5", OffsetMin = "-634.3 244.22", OffsetMax = "-365.117 351.9" } }, "Hud", "EventUI"); container.Add(new CuiElement { Name = "Title", Parent = "EventUI", Components = { new CuiTextComponent { Text = Title, Font = "robotocondensed-bold.ttf", FontSize = 14, Align = TextAnchor.MiddleLeft, Color = "0.949 0.901 0.862 1" }, new CuiRectTransformComponent { AnchorMin = "0.5 0.5", AnchorMax = "0.5 0.5", OffsetMin = "-130.565 29.734", OffsetMax = "130.565 53.84" } } }); container.Add(new CuiElement { Name = "Description", Parent = "EventUI", Components = { new CuiTextComponent { Text = Description, Font = "robotocondensed-bold.ttf", FontSize = 11, Align = TextAnchor.UpperLeft, Color = "0.949 0.901 0.862 1" }, //0.7490196 0.7490196 0.6745098 1 new CuiRectTransformComponent { AnchorMin = "0.5 0.5", AnchorMax = "0.5 0.5", OffsetMin = "-120.923 -3.194", OffsetMax = "130.564 29.734" } } }); container.Add(new CuiElement { Name = "NPC", Parent = "EventUI", Components = { new CuiTextComponent { Text = "Scientist Alive: " + NPCs + " Loots: 20", Font = "robotocondensed-bold.ttf", FontSize = 11, Align = TextAnchor.UpperLeft, Color = "0.949 0.901 0.862 1" }, new CuiRectTransformComponent { AnchorMin = "0.5 0.5", AnchorMax = "0.5 0.5", OffsetMin = "-120.923 -20.28", OffsetMax = "130.564 -3.194" } } }); container.Add(new CuiElement { Name = "Image_8085", Parent = "EventUI", Components = { new CuiRawImageComponent { Color = "0.7490196 0.7490196 0.6745098 1", Png = GetImage("https://i.postimg.cc/W3mGVmQ4/alarm-clock.png")}, new CuiRectTransformComponent { AnchorMin = "0.5 0.5", AnchorMax = "0.5 0.5", OffsetMin = "-120.92 -46.7", OffsetMax = "-95.92 -21.7" } } }); container.Add(new CuiElement { Name = "Time", Parent = "EventUI", Components = { new CuiTextComponent { Text = FormatTime(countdown), Font = "robotocondensed-bold.ttf", FontSize = 14, Align = TextAnchor.MiddleLeft, Color = "0.949 0.901 0.862 1" }, new CuiRectTransformComponent { AnchorMin = "0.5 0.5", AnchorMax = "0.5 0.5", OffsetMin = "-95.92 -46.7", OffsetMax = "9.193 -21.701" } } }); CuiHelper.DestroyUi(player, "EventUI"); CuiHelper.AddUi(player, container); } #endregion #region OxideHooks private void OnPlayerDeath(BasePlayer player, HitInfo hit) { if (monumentEvents.Count == 0) return; foreach (var monument in monumentEvents.Values) { monument.OnPlayerDeath(player, hit); } } private void OnPlayerConnected(BasePlayer player) { if (monumentEvents.Count == 0) return; foreach (var monument in monumentEvents.Values) { monument.OnPlayerConnected(player); } } private void OnPlayerDisconnected(BasePlayer player) { if (monumentEvents.Count == 0) return; foreach (var monument in monumentEvents.Values) { monument.OnPlayerDisconnected(player); } } private object OnMapMarkerRemove(BasePlayer player, List notes, int index) { if (monumentEvents.Count == 0 || notes == null || index < 0 || index >= notes.Count) return null; MapNote note = notes[index]; bool noteRemoved = false; foreach (var monument in monumentEvents.Values) { var result = monument.OnMapMarkerRemove(player, note); if (result is bool && (bool)result == true) { noteRemoved = true; break; } } return noteRemoved ? (object)true : null; } private void OnEntityKill(ScientistNPC npc) { if (monumentEvents.Count < 1) return; foreach (var eve in monumentEvents.Values) { if (eve.IsEventNpc(npc)) { eve.scientistNpcs.Remove(npc); } } } private object CanHelicopterUseNapalm(PatrolHelicopterAI heli) { if (IsRoamHeli(heli.helicopterBase)) return true; return null; } private void OnCrateHack(HackableLockedCrate crate) { if (crate == null || monumentEvents == null || monumentEvents.Count < 1) return; foreach (var events in monumentEvents) { MonumentEvent monument = events.Value; if (monument == null || monument.hackableLockedCrates == null || monument.hackableLockedCrates.Count == 0) continue; foreach (var ent in monument.hackableLockedCrates) { if (ent == null || ent.net?.ID != crate.net?.ID) continue; MonumentInfo monumentInfo = monument.FindMonumentByName(events.Key); if (monumentInfo == null) continue; monument.SendWave(); return; // Exit once the matching crate is found and handled } } } #endregion #region Behaviours private class PatrolHeliRoam : FacepunchBehaviour { internal PatrolHelicopterAI patrolHelicopterAI; internal PatrolHelicopter patrolHelicopter; internal MonumentInfo monument; int outsideTime = 0; public static PatrolHeliRoam CreateHelicopter(MonumentInfo info, Vector3 position) { PatrolHelicopter patrolHelicopter = GameManager.server.CreateEntity("assets/prefabs/npc/patrol helicopter/patrolhelicopter.prefab", position + new Vector3(0, 200, 0), Quaternion.identity) as PatrolHelicopter; patrolHelicopter.enableSaving = false; //patrolHelicopter.skinID = 755446; patrolHelicopter.Spawn(); patrolHelicopter.transform.position = position + ins.SetPositionInRange(-1000, 1000, 400); PatrolHeliRoam convoyHeli = patrolHelicopter.gameObject.AddComponent(); convoyHeli.InitHelicopter(patrolHelicopter, info); return convoyHeli; } internal void InitHelicopter(PatrolHelicopter Helicopter, MonumentInfo monu) { patrolHelicopter = Helicopter; patrolHelicopterAI = Helicopter.myAI; monument = monu; /*Vector3 coordinates = monu.transform.position; if (coordinates != Vector3.zero) { //if (coordinates.y < 225) coordinates.y = 225; //patrolHelicopterAI.SetInitialDestination(coordinates, 0.25f); //patrolHelicopter.transform.position = patrolHelicopterAI.transform.position = coordinates; }*/ patrolHelicopterAI.interestZoneOrigin = monu.transform.position; patrolHelicopterAI.ExitCurrentState(); InvokeRepeating(UpdateDestination, 1f, 1f); } internal ulong GetHeliNetId() { return patrolHelicopter.net.ID.Value; } internal void UpdateDestination() { if (patrolHelicopter.myAI._currentState == PatrolHelicopterAI.aiState.DEATH || patrolHelicopter.myAI._currentState == PatrolHelicopterAI.aiState.STRAFE) return; Vector3 targetPosition = monument.transform.position + new Vector3(0, 50f, 0); float distanceToTargetPosition = Vector3.Distance(targetPosition, patrolHelicopter.transform.position); if (patrolHelicopterAI.leftGun.HasTarget() || patrolHelicopterAI.rightGun.HasTarget()) { if (distanceToTargetPosition > 300) { outsideTime++; if (outsideTime > Oxide.Core.Random.Range(20, 35)) patrolHelicopter.myAI.State_Move_Enter(targetPosition); } else { outsideTime = 0; } } else if (distanceToTargetPosition > 300) { patrolHelicopterAI.State_Move_Enter(targetPosition); outsideTime = 0; } else if (distanceToTargetPosition <= 30) { //patrolHelicopterAI.SetIdealRotation(patrolHelicopterAI.GetYawRotationTo(targetPosition), 100); patrolHelicopterAI.State_Move_Enter(ins.GetRandomVectorAroundPoint(targetPosition, 200, 300)); } else { outsideTime = 0; } } internal void KillHeli() { if (patrolHelicopter != null && !patrolHelicopter.IsDestroyed) patrolHelicopter.Kill(); } } private class MilitaryCH47Crate : MonoBehaviour { private MonumentEvent MonumentEvent; private CH47Helicopter ch47; private CH47AIBrain brain; private Vector3 position; private MonumentInfo monument; private CH47HelicopterAIController aiController; private SavedSettings.MonumentSetting monuConfig; private void Awake() { ch47 = GetComponent(); brain = GetComponent(); aiController = GetComponent(); aiController.CancelInvoke(aiController.SpawnScientists); brain.SetEnabled(false); } private void Start() { AddStates(); } private void OnDestroy() { if (ch47 && !ch47.IsDestroyed) ch47.Kill(); } #region Public Methods public void Initialize(Vector3 landingPoint, MonumentEvent events) { MonumentEvent = events; position = landingPoint; monument = events.monumentInfo; monuConfig = events.monumentConfig; brain.SetEnabled(true); } public void Kill() { if (brain != null) { brain.SetEnabled(false); // Disable the AI brain } if (aiController != null && !aiController.IsDestroyed) { aiController.DelayedKill(); // Destroy the helicopter } Destroy(this); } #endregion #region Helpers private void DropCrate() { for (int i = 0; i < monuConfig.crateSetting.hackableAmount; i++) { Vector3 newPos = ins.GetRandomFromMono(monument.transform, monuConfig.crateSetting.hackableLocations).GetRandom(); var ent = GameManager.server.CreateEntity("assets/prefabs/deployable/chinooklockedcrate/codelockedhackablecrate.prefab", position + new Vector3(0, 1, 0), new Quaternion(), true); HackableLockedCrate crate = ent.GetComponent(); crate.hackSeconds = HackableLockedCrate.requiredHackSeconds - float.Parse(monuConfig.crateSetting.spawnHackableCrateTimer.ToString()); crate.Spawn(); MonumentEvent.hackableLockedCrates.Add(crate); } ins.SendMessage("EventCrateLanded", MonumentEvent); MonumentEvent.description = ins.GetLang("EventCrateLanded").Replace("{EVENT_NAME}", monument.displayPhrase.english); } #endregion #region States private void AddStates() { brain.states.Clear(); brain.AddState(new LandState(position, monument.transform)); brain.AddState(new EgressState(this, position)); } private class LandState : BaseAIBrain.BasicAIState { private float landedForSeconds; private float landingTime; private float landingHeight; private float nextDismountTime; private Vector3 landPos; private Transform transform; private float landingDuration = 8f; public LandState(Vector3 position, Transform monument) : base(AIState.Land) { landPos = position; transform = monument; } public override float GetWeight() { if (base.IsInState() && landedForSeconds < landingDuration) return 1000f; if (!base.IsInState() && (Time.time - landingTime) > 10f) return 9000f; return 0f; } public override void StateEnter(BaseAIBrain brain, BaseEntity entity) { brain.mainInterestPoint = landPos; landingHeight = 15f; /// can we change it to landPos.y? CH47HelicopterAIController aIController = entity as CH47HelicopterAIController; aIController.SetMinHoverHeight(30f); base.StateEnter(brain, entity); } public override void StateLeave(BaseAIBrain brain, BaseEntity entity) { CH47HelicopterAIController aiController = entity as CH47HelicopterAIController; aiController.EnableFacingOverride(false); aiController.SetAltitudeProtection(true); aiController.SetMinHoverHeight(30); landedForSeconds = 0f; base.StateLeave(brain, entity); } public override StateStatus StateThink(float delta, BaseAIBrain brain, BaseEntity entity) { base.StateThink(delta, brain, entity); CH47HelicopterAIController aiController = entity as CH47HelicopterAIController; Vector3 position = aiController.transform.position; float distance = Vector3Ex.Distance2D(landPos, position); bool altitudeProtection = distance > 40f; aiController.EnableFacingOverride(distance < 20f); aiController.SetAltitudeProtection(altitudeProtection); if (!altitudeProtection) aiController.SetMinHoverHeight(landingHeight); bool isOnGround = Mathf.Abs(landPos.y - position.y) < 3f && distance <= 5f && aiController.rigidBody.velocity.magnitude < 1f; if (isOnGround) { landedForSeconds += delta; if (landingTime == 0) landingTime = Time.time; } landingHeight -= 4f * (1f - Mathf.InverseLerp(0f, 7f, distance)) * Time.deltaTime; if (landingHeight < -5f) landingHeight = -5f; aiController.SetAimDirection(transform.right); aiController.SetMoveTarget(brain.mainInterestPoint + new Vector3(0f, landingHeight, 0f)); if (isOnGround) { if (landedForSeconds > landingDuration) brain.GetComponent().ForceSetAge(float.PositiveInfinity); } return StateStatus.Running; } } private class EgressState : BaseAIBrain.BasicAIState { private MilitaryCH47Crate ch47; private Vector3 landingPoint; private bool killing; private bool dropped; public EgressState(MilitaryCH47Crate ch47, Vector3 position) : base(AIState.Egress) { this.ch47 = ch47; this.landingPoint = position; } public override float GetWeight() { CH47AIBrain component = brain.GetComponent(); if (component == null || component.Age <= 1800f) return 0f; return 10000f; } public override void StateEnter(BaseAIBrain brain, BaseEntity entity) { CH47HelicopterAIController aiController = entity as CH47HelicopterAIController; aiController.EnableFacingOverride(false); aiController.SetAltitudeProtection(true); Transform transform = aiController.transform; Rigidbody rigidBody = aiController.rigidBody; Vector3 rhs = (rigidBody.velocity.magnitude < 0.1f) ? transform.forward : rigidBody.velocity.normalized; Vector3 a = Vector3.Cross(Vector3.Cross(transform.up, rhs), Vector3.up); Vector3 position = transform.position + a * 8000f; float altitude = Mathf.Max(TerrainMeta.WaterMap.GetHeight(position), TerrainMeta.HeightMap.GetHeight(position)); altitude += 100f; position.y = altitude; brain.mainInterestPoint = position; aiController.SetMoveTarget(brain.mainInterestPoint); base.StateEnter(brain, entity); } public override StateStatus StateThink(float delta, BaseAIBrain brain, BaseEntity entity) { base.StateThink(delta, brain, entity); if (killing) return StateStatus.Running; CH47HelicopterAIController aiController = entity as CH47HelicopterAIController; float currentAltitude = aiController.transform.position.y - landingPoint.y; if (!dropped && currentAltitude > 8f) { dropped = true; ch47.DropCrate(); } aiController.SetMoveTarget(brain.mainInterestPoint); if (base.TimeInState > 120f) { aiController.DelayedKill(); killing = true; } return StateStatus.Running; } } #endregion } private class MilitaryCH47NPCs : MonoBehaviour { private MonumentEvent MonumentEvent; private CH47Helicopter ch47; private CH47AIBrain brain; private Vector3 position; private MonumentInfo monument; private CH47HelicopterAIController aiController; private SavedSettings.MonumentSetting monuConfig; private void Awake() { ch47 = GetComponent(); brain = GetComponent(); aiController = GetComponent(); aiController.CancelInvoke(aiController.SpawnScientists); brain.SetEnabled(false); } private void Start() { AddStates(); } private void OnDestroy() { if (ch47 && !ch47.IsDestroyed) ch47.Kill(); } #region Public Methods public void Initialize(Vector3 landingPoint, MonumentEvent events) { MonumentEvent = events; position = landingPoint; monument = events.monumentInfo; monuConfig = events.monumentConfig; brain.SetEnabled(true); } public void Kill() { if (brain != null) { brain.SetEnabled(false); // Disable the AI brain } if (aiController != null && !aiController.IsDestroyed) { aiController.DelayedKill(); // Destroy the helicopter } Destroy(this); } #endregion #region Helpers private void DropNPCS() { if (monuConfig.waveSetting.npcAmount > 0) { int positionIndex = 0; for (int i = 0; i < monuConfig.waveSetting.npcAmount; i++) { NpcConfig npcConfig = ins.GetNpcConfigByID(monuConfig.waveSetting.npcIDs.GetRandom()); Vector3 randomPosition = ins.GetLocalToWorldMatrix(monument.transform, monuConfig.npcSetting.npcPositions[positionIndex]); positionIndex = (positionIndex + 1) % monuConfig.npcSetting.npcPositions.Count; ScientistNPC npc = (ScientistNPC)ins.NpcSpawn.Call("SpawnNpc", randomPosition, ins.GetObjectConfig(npcConfig)); MonumentEvent.scientistNpcs.Add(npc); if (npc != null) { Vector3 telp = (Vector3)ins.FindPointOnNavmesh(position + new Vector3(Oxide.Core.Random.Range(1f, 10f), 0f, Oxide.Core.Random.Range(1f, 10f))); npc.Teleport(telp); } } } } #endregion #region States private void AddStates() { brain.states.Clear(); brain.AddState(new LandState(position, monument.transform)); brain.AddState(new EgressState(this, position)); } private class LandState : BaseAIBrain.BasicAIState { private float landedForSeconds; private float landingTime; private float landingHeight; private float nextDismountTime; private Vector3 landPos; private Transform transform; private float landingDuration = 8f; public LandState(Vector3 position, Transform monument) : base(AIState.Land) { landPos = position; transform = monument; } public override float GetWeight() { if (base.IsInState() && landedForSeconds < landingDuration) return 1000f; if (!base.IsInState() && (Time.time - landingTime) > 10f) return 9000f; return 0f; } public override void StateEnter(BaseAIBrain brain, BaseEntity entity) { brain.mainInterestPoint = landPos; landingHeight = 15f; /// can we change it to landPos.y? CH47HelicopterAIController aIController = entity as CH47HelicopterAIController; aIController.SetMinHoverHeight(30f); base.StateEnter(brain, entity); } public override void StateLeave(BaseAIBrain brain, BaseEntity entity) { CH47HelicopterAIController aiController = entity as CH47HelicopterAIController; aiController.EnableFacingOverride(false); aiController.SetAltitudeProtection(true); aiController.SetMinHoverHeight(30); landedForSeconds = 0f; base.StateLeave(brain, entity); } public override StateStatus StateThink(float delta, BaseAIBrain brain, BaseEntity entity) { base.StateThink(delta, brain, entity); CH47HelicopterAIController aiController = entity as CH47HelicopterAIController; Vector3 position = aiController.transform.position; float distance = Vector3Ex.Distance2D(landPos, position); bool altitudeProtection = distance > 40f; aiController.EnableFacingOverride(distance < 20f); aiController.SetAltitudeProtection(altitudeProtection); if (!altitudeProtection) aiController.SetMinHoverHeight(landingHeight); bool isOnGround = Mathf.Abs(landPos.y - position.y) < 3f && distance <= 5f && aiController.rigidBody.velocity.magnitude < 1f; if (isOnGround) { landedForSeconds += delta; if (landingTime == 0) landingTime = Time.time; } landingHeight -= 4f * (1f - Mathf.InverseLerp(0f, 7f, distance)) * Time.deltaTime; if (landingHeight < -5f) landingHeight = -5f; aiController.SetAimDirection(transform.right); aiController.SetMoveTarget(brain.mainInterestPoint + new Vector3(0f, landingHeight, 0f)); if (isOnGround) { if (landedForSeconds > landingDuration) brain.GetComponent().ForceSetAge(float.PositiveInfinity); } return StateStatus.Running; } } private class EgressState : BaseAIBrain.BasicAIState { private MilitaryCH47NPCs ch47; private Vector3 landingPoint; private bool killing; private bool dropped; public EgressState(MilitaryCH47NPCs ch47, Vector3 position) : base(AIState.Egress) { this.ch47 = ch47; this.landingPoint = position; } public override float GetWeight() { CH47AIBrain component = brain.GetComponent(); if (component == null || component.Age <= 1800f) return 0f; return 10000f; } public override void StateEnter(BaseAIBrain brain, BaseEntity entity) { CH47HelicopterAIController aiController = entity as CH47HelicopterAIController; aiController.EnableFacingOverride(false); aiController.SetAltitudeProtection(true); Transform transform = aiController.transform; Rigidbody rigidBody = aiController.rigidBody; Vector3 rhs = (rigidBody.velocity.magnitude < 0.1f) ? transform.forward : rigidBody.velocity.normalized; Vector3 a = Vector3.Cross(Vector3.Cross(transform.up, rhs), Vector3.up); Vector3 position = transform.position + a * 8000f; float altitude = Mathf.Max(TerrainMeta.WaterMap.GetHeight(position), TerrainMeta.HeightMap.GetHeight(position)); altitude += 100f; position.y = altitude; brain.mainInterestPoint = position; aiController.SetMoveTarget(brain.mainInterestPoint); base.StateEnter(brain, entity); } public override StateStatus StateThink(float delta, BaseAIBrain brain, BaseEntity entity) { base.StateThink(delta, brain, entity); if (killing) return StateStatus.Running; CH47HelicopterAIController aiController = entity as CH47HelicopterAIController; float currentAltitude = aiController.transform.position.y - landingPoint.y; if (!dropped && currentAltitude > 8f) { dropped = true; ch47.DropNPCS(); } aiController.SetMoveTarget(brain.mainInterestPoint); if (base.TimeInState > 120f) { aiController.DelayedKill(); killing = true; } return StateStatus.Running; } } #endregion } private class CallChinok : FacepunchBehaviour { internal CH47HelicopterAIController chCopterAI; internal CH47Helicopter chCopter; internal CH47AIBrain brain; internal MonumentInfo monument; internal SavedSettings.MonumentSetting monuConfig; internal int totalCrate = 0; internal Vector3 CratePosition; internal Vector3 heightVector; internal static MonumentEvent MEvent; internal bool landing = false; internal Timer timerSpawn, timerLeave; public static CallChinok CreateCh47(MonumentInfo info, SavedSettings.MonumentSetting mConfig, MonumentEvent eve, Vector3 CratePosition) { CH47Helicopter ch47spawn = GameManager.server.CreateEntity("assets/prefabs/npc/ch47/ch47scientists.entity.prefab", info.transform.position + ins.SetPositionInRange(-600, 600, 300)) as CH47Helicopter; //ins.Puts("Created"); ch47spawn.Spawn(); CallChinok ch47Heli = ch47spawn.gameObject.AddComponent(); ch47Heli.StartCh47DropBox(ch47spawn, mConfig, info ,CratePosition); MEvent = eve; return ch47Heli; } internal void StartCh47DropBox(CH47Helicopter Helicopter, SavedSettings.MonumentSetting mConfig, MonumentInfo monumen, Vector3 cratePos) { brain = Helicopter.GetComponent(); chCopter = Helicopter; chCopterAI = Helicopter.GetComponent(); monument = monumen; monuConfig = mConfig; CratePosition = cratePos; totalCrate = mConfig.crateSetting.hackableLocations.Count; heightVector = new Vector3(CratePosition.x, CratePosition.y += 10f, CratePosition.z); chCopterAI.SetLandingTarget(heightVector); InvokeRepeating(UpdateTarget, 1f, 1f); } internal void UpdateTarget() { //ins.Puts("d"); if (chCopter == null || chCopterAI == null) { //ins.Puts("Nope"); KillCH47(); return; } //ins.Puts($"{Vector3.Distance(chCopterAI.transform.position, heightVector)} + {chCopterAI.transform.position} = {new Vector3(heightVector.x, chCopterAI.transform.position.y, heightVector.z)}"); if (Vector3.Distance(chCopterAI.transform.position, new Vector3(heightVector.x, chCopterAI.transform.position.y, heightVector.z)) <= 3 && !landing) { landing = true; //ins.Puts("done"); timerSpawn = ins.timer.In(5, () => { for (int i = 0; i < monuConfig.crateSetting.hackableAmount; i++) { Vector3 newPos = ins.GetRandomFromMono(monument.transform, monuConfig.crateSetting.hackableLocations).GetRandom(); var ent = GameManager.server.CreateEntity("assets/prefabs/deployable/chinooklockedcrate/codelockedhackablecrate.prefab", CratePosition, new Quaternion(), true); HackableLockedCrate crate = ent.GetComponent(); crate.hackSeconds = HackableLockedCrate.requiredHackSeconds - float.Parse(monuConfig.crateSetting.spawnHackableCrateTimer.ToString()); crate.Spawn(); MEvent.hackableLockedCrates.Add(crate); } }); timerLeave = ins.timer.In(10, () => { ins.SendMessage("EventCrateLanded", MEvent); // MEvent.description = ins.GetLang("EventCrateLanded").Replace("{EVENT_NAME}", monument.displayPhrase.english); chCopterAI.ClearLandingTarget(); }); ins.timer.In(60, () => { ins.Puts("killed"); KillCH47(); }); } } internal void KillCH47() { if (timerSpawn != null) timerSpawn.Destroy(); if (timerLeave != null) timerLeave.Destroy(); if (chCopter != null) chCopter?.Kill(); if (chCopterAI != null) chCopterAI?.Kill(); } } private class CallChinokNPCs : FacepunchBehaviour { internal CH47HelicopterAIController chCopterAI; internal CH47Helicopter chCopter; internal MonumentInfo monument; internal SavedSettings.MonumentSetting monuConfig; internal Vector3 LandingPos; internal Vector3 heightVector; internal static MonumentEvent MEvent; internal bool landing = false; internal Timer timerSpawn, timerLeave; public static CallChinokNPCs CreateCh47NPCs(MonumentInfo info, SavedSettings.MonumentSetting mConfig, MonumentEvent eve) { CH47Helicopter ch47spawn = GameManager.server.CreateEntity("assets/prefabs/npc/ch47/ch47scientists.entity.prefab", info.transform.position + ins.SetPositionInRange(-600, 600, 300)) as CH47Helicopter; //ins.Puts("Created"); ch47spawn.Spawn(); CallChinokNPCs ch47Heli = ch47spawn.gameObject.AddComponent(); ch47Heli.StartCh47DropBox(ch47spawn, mConfig, info); MEvent = eve; return ch47Heli; } internal void StartCh47DropBox(CH47Helicopter Helicopter, SavedSettings.MonumentSetting mConfig, MonumentInfo monumen) { chCopter = Helicopter; chCopterAI = Helicopter.GetComponent(); monument = monumen; monuConfig = mConfig; LandingPos = ins.GetRandomFromMono(monument.transform, monuConfig.waveSetting.npcDropLocations).GetRandom(); heightVector = new Vector3(LandingPos.x, LandingPos.y += 15f, LandingPos.z); chCopterAI.SetLandingTarget(heightVector); InvokeRepeating(UpdateTarget, 1f, 1f); } internal void UpdateTarget() { //ins.Puts("d"); if (chCopter == null || chCopterAI == null) { //ins.Puts("Nope"); KillCH47(); return; } //ins.Puts($"{Vector3.Distance(chCopterAI.transform.position, heightVector)} + {chCopterAI.transform.position} = {new Vector3(heightVector.x, chCopterAI.transform.position.y, heightVector.z)}"); if (Vector3.Distance(chCopterAI.transform.position, new Vector3(heightVector.x, chCopterAI.transform.position.y, heightVector.z)) <= 5 && !landing) { landing = true; //ins.Puts("doned"); timerSpawn = ins.timer.In(5, () => { if (monuConfig.waveSetting.npcAmount > 0) { int positionIndex = 0; for (int i = 0; i < monuConfig.waveSetting.npcAmount; i++) { NpcConfig npcConfig = ins.GetNpcConfigByID(monuConfig.waveSetting.npcIDs.GetRandom()); Vector3 randomPosition = ins.GetLocalToWorldMatrix(monument.transform, monuConfig.npcSetting.npcPositions[positionIndex]); positionIndex = (positionIndex + 1) % monuConfig.npcSetting.npcPositions.Count; ScientistNPC npc = (ScientistNPC)ins.NpcSpawn.Call("SpawnNpc", randomPosition, ins.GetObjectConfig(npcConfig)); MEvent.scientistNpcs.Add(npc); if (npc != null) { Vector3 telp = (Vector3)ins.FindPointOnNavmesh(LandingPos + new Vector3(Oxide.Core.Random.Range(4f, 10f), 0f, Oxide.Core.Random.Range(3f, 10f))); npc.Teleport(telp); } } } }); timerLeave = ins.timer.In(15, () => { chCopterAI.ClearLandingTarget(); }); ins.timer.In(60, () => { //ins.Puts("killed"); KillCH47(); }); } } internal void KillCH47() { chCopterAI.ClearLandingTarget(); if (timerSpawn != null) timerSpawn.Destroy(); if (timerLeave != null) timerLeave.Destroy(); if (chCopterAI != null && !chCopterAI.IsDestroyed) chCopterAI.Kill(); if (chCopter != null && !chCopter.IsDestroyed) chCopter.Kill(); } } #endregion #region Extensions private class MonumentTrigger : MonoBehaviour { public MonumentEvent monumentEvent; public void OnTriggerEnter(Collider collider) { if (!collider) return; if (!collider.TryGetComponent(out var player)) return; if (!player.userID.IsSteamId()) return; monumentEvent.PlayersIN.Add(player); if (player != null && player.IsConnected) { player?.SendConsoleCommand("gametip.showgametip", ins.GetLang("EventJoinPlayer")); ins.timer.Once(3, () => { player?.SendConsoleCommand("gametip.hidegametip"); }); } } public void OnTriggerExit(Collider collider) { if (!collider) return; if (!collider.TryGetComponent(out var player)) return; if (!player.userID.IsSteamId()) return; monumentEvent.PlayersIN.Remove(player); if (player != null && player.IsConnected) { player?.SendConsoleCommand("gametip.showgametip", ins.GetLang("EventLeavePlayer")); ins.timer.Once(3, () => { player?.SendConsoleCommand("gametip.hidegametip"); }); } } } private T CreateEntity(string prefab, Vector3 position, Quaternion rotation = default) where T : BaseEntity { BaseEntity entity = GameManager.server.CreateEntity(prefab, position, rotation, true); entity.enableSaving = false; return entity as T; } #endregion } }