using System; using UnityEngine; using System.Collections.Generic; using System.Linq; using Facepunch; using Newtonsoft.Json; using Oxide.Core; using System.Globalization; using Oxide.Core.Libraries.Covalence; using Rust; using Oxide.Core.Configuration; namespace Oxide.Plugins { [Info("Monuments Watcher", "IIIaKa", "0.1.3")] [Description("A plugin creating a bounding box around monuments and tracking entry and exit of entities from it.")] class MonumentsWatcher : RustPlugin { #region ~Variables~ private static MonumentsWatcher Instance { get; set; } private DynamicConfigFile _monumentsConfig; private Hash _monumentsBounds; private const string PERMISSION_ADMIN = "monumentswatcher.admin"; private Hash _monumentsList = new Hash(); private static Hash _playersInMonuments; private static Hash _entitiesInMonuments; private static Dictionary _defaultBounds = new Dictionary() { { "airfield_1", new BoundsValues(new Vector3(0, 15, -25), new Vector3(360, 60, 210)) }, { "arctic_research_base_a", new BoundsValues(new Vector3(-2, 15, -2), new Vector3(120, 40, 115)) }, { "bandit_town", new BoundsValues(new Vector3(0, 15, -5), new Vector3(160, 45, 160)) }, { "compound", new BoundsValues(new Vector3(0, 15, 0), new Vector3(180, 60, 200)) }, { "desert_military_base_a", new BoundsValues(new Vector3(0, 20, 3), new Vector3(100, 45, 100)) }, { "desert_military_base_b", new BoundsValues(new Vector3(0, 20, 3), new Vector3(100, 45, 100)) }, { "desert_military_base_c", new BoundsValues(new Vector3(0, 20, 3), new Vector3(100, 45, 100)) }, { "desert_military_base_d", new BoundsValues(new Vector3(0, 20, 3), new Vector3(100, 45, 100)) }, { "excavator_1", new BoundsValues(new Vector3(20, 35, -23), new Vector3(245, 100, 245)) }, { "ferry_terminal_1", new BoundsValues(new Vector3(4, 12, 18), new Vector3(215, 45, 205)) }, { "fishing_village_a", new BoundsValues(new Vector3(-2, 5, 0), new Vector3(85, 40, 90)) }, { "fishing_village_b", new BoundsValues(new Vector3(-3, 5, 4), new Vector3(60, 40, 90)) }, { "fishing_village_c", new BoundsValues(new Vector3(-3, 5, 4), new Vector3(60, 40, 90)) }, { "gas_station_1", new BoundsValues(new Vector3(0, 14, 14), new Vector3(70, 25, 60)) }, { "harbor_1", new BoundsValues(new Vector3(0, 20, 42), new Vector3(235, 60, 265)) }, { "harbor_2", new BoundsValues(new Vector3(20, 20, 12), new Vector3(230, 60, 260)) }, { "junkyard_1", new BoundsValues(new Vector3(0, 15, 10), new Vector3(180, 40, 180)) }, { "launch_site_1", new BoundsValues(new Vector3(10, 28, -25), new Vector3(555, 125, 290)) }, { "lighthouse", new BoundsValues(new Vector3(8, 35, 2), new Vector3(60, 80, 60)) }, { "military_tunnel_1", new BoundsValues(new Vector3(0, 20, -20), new Vector3(270, 85, 245)) }, { "mining_quarry_a", new BoundsValues(new Vector3(0, 10, 0), new Vector3(65, 25, 75)) }, { "mining_quarry_b", new BoundsValues(new Vector3(-5, 10, -2), new Vector3(65, 25, 60)) }, { "mining_quarry_c", new BoundsValues(new Vector3(-5, 10, 5), new Vector3(50, 25, 65)) }, { "nuclear_missile_silo", new BoundsValues(new Vector3(10, 15, 0), new Vector3(140, 100, 120)) }, { "oilrigai", new BoundsValues(new Vector3(18, 25, -2), new Vector3(75, 70, 85)) }, { "oilrigai2", new BoundsValues(new Vector3(3, 40, 12), new Vector3(85, 95, 120)) }, { "powerplant_1", new BoundsValues(new Vector3(-15, 30, -13), new Vector3(220, 75, 300)) }, { "radtown_small_3", new BoundsValues(new Vector3(0, 20, -13), new Vector3(150, 55, 150)) }, { "satellite_dish", new BoundsValues(new Vector3(0, 25, 5), new Vector3(170, 60, 140)) }, { "sphere_tank", new BoundsValues(new Vector3(0, 41, 5), new Vector3(110, 85, 110)) }, { "stables_a", new BoundsValues(new Vector3(5, 15, -3), new Vector3(70, 30, 80)) }, { "stables_b", new BoundsValues(new Vector3(8, 15, 8), new Vector3(85, 30, 80)) }, { "station-sn-0", new BoundsValues(new Vector3(0, 8, 0), new Vector3(215, 18, 105)) }, { "station-sn-1", new BoundsValues(new Vector3(0, 8, 0), new Vector3(215, 18, 105)) }, { "station-sn-2", new BoundsValues(new Vector3(0, 8, 0), new Vector3(215, 18, 105)) }, { "station-sn-3", new BoundsValues(new Vector3(0, 8, 0), new Vector3(215, 18, 105)) }, { "station-we-0", new BoundsValues(new Vector3(0, 8, 0), new Vector3(215, 18, 105)) }, { "station-we-1", new BoundsValues(new Vector3(0, 8, 0), new Vector3(215, 18, 105)) }, { "station-we-2", new BoundsValues(new Vector3(0, 8, 0), new Vector3(215, 18, 105)) }, { "station-we-3", new BoundsValues(new Vector3(0, 8, 0), new Vector3(215, 18, 105)) }, { "supermarket_1", new BoundsValues(new Vector3(2, 5, 0), new Vector3(50, 15, 50)) }, { "swamp_a", new BoundsValues(new Vector3(-11, 15, 3), new Vector3(160, 35, 190)) }, { "swamp_b", new BoundsValues(new Vector3(-1, 15, -3), new Vector3(125, 35, 125)) }, { "swamp_c", new BoundsValues(new Vector3(6, 15, -1), new Vector3(130, 35, 130)) }, { "trainyard_1", new BoundsValues(new Vector3(5, 25, -5), new Vector3(250, 80, 230)) }, { "underwater_lab_a", new BoundsValues(new Vector3(0, 15, 0), new Vector3(110, 25, 110)) }, { "underwater_lab_b", new BoundsValues(new Vector3(0, 15, 0), new Vector3(110, 25, 110)) }, { "underwater_lab_c", new BoundsValues(new Vector3(0, 15, 0), new Vector3(110, 25, 110)) }, { "underwater_lab_d", new BoundsValues(new Vector3(0, 15, 0), new Vector3(110, 25, 110)) }, { "warehouse", new BoundsValues(new Vector3(0, 5, -7), new Vector3(45, 15, 30)) }, { "water_treatment_plant_1", new BoundsValues(new Vector3(0, 30, -30), new Vector3(250, 90, 290)) }, { "entrance_bunker_a", new BoundsValues(new Vector3(-4, 1, -1), new Vector3(20, 30, 20)) }, { "entrance_bunker_b", new BoundsValues(new Vector3(-8, 1, 0), new Vector3(30, 30, 20)) }, { "entrance_bunker_c", new BoundsValues(new Vector3(-4, 1, -1), new Vector3(20, 30, 20)) }, { "entrance_bunker_d", new BoundsValues(new Vector3(-4, 1, -1), new Vector3(20, 30, 20)) }, { "cave_small_easy", new BoundsValues(new Vector3(6, -28, 17), new Vector3(45, 46, 66)) }, { "cave_small_medium", new BoundsValues(new Vector3(20, -30, -18), new Vector3(80, 50, 65)) }, { "cave_small_hard", new BoundsValues(new Vector3(8, -21, 0), new Vector3(45, 35, 80)) }, { "cave_medium_easy", new BoundsValues(new Vector3(8, -21, 0), new Vector3(45, 35, 80)) }, { "cave_medium_medium", new BoundsValues(new Vector3(-1, -25, 2), new Vector3(110, 50, 110)) }, { "cave_medium_hard", new BoundsValues(new Vector3(8, -21, 0), new Vector3(45, 35, 80)) }, { "cave_large_medium", new BoundsValues(new Vector3(8, -21, 0), new Vector3(45, 35, 80)) }, { "cave_large_hard", new BoundsValues(new Vector3(8, -21, 0), new Vector3(45, 35, 80)) }, { "cave_large_sewers_hard", new BoundsValues(new Vector3(50, -25, -7), new Vector3(170, 40, 165)) }, { "ice_lake_1", new BoundsValues(new Vector3(-2, 15, 0), new Vector3(140, 35, 160)) }, { "ice_lake_2", new BoundsValues(new Vector3(0, 15, 0), new Vector3(150, 35, 150)) }, { "ice_lake_3", new BoundsValues(new Vector3(0, 15, 0), new Vector3(180, 35, 240)) }, { "ice_lake_4", new BoundsValues(new Vector3(0, 15, 0), new Vector3(85, 35, 85)) }, { "power_sub_small_1", new BoundsValues(new Vector3(0, 5, 0), new Vector3(15, 10, 15)) }, { "power_sub_small_2", new BoundsValues(new Vector3(0, 5, 0), new Vector3(15, 10, 15)) }, { "power_sub_big_1", new BoundsValues(new Vector3(0, 5, 1), new Vector3(20, 10, 22)) }, { "power_sub_big_2", new BoundsValues(new Vector3(-1, 5, 1), new Vector3(23, 10, 22)) }, { "water_well_a", new BoundsValues(new Vector3(-2, 7, 0), new Vector3(25, 20, 25)) }, { "water_well_b", new BoundsValues(new Vector3(-1, 7, 0), new Vector3(25, 20, 25)) }, { "water_well_c", new BoundsValues(new Vector3(0, 10, 1), new Vector3(30, 25, 30)) }, { "water_well_d", new BoundsValues(new Vector3(0, 10, 1), new Vector3(30, 25, 30)) }, { "water_well_e", new BoundsValues(new Vector3(-1, 7, 0), new Vector3(25, 20, 25)) } }; #endregion #region ~Configuration~ private static Configuration _config; private class Configuration { [JsonProperty(PropertyName = "MonumentsWatcher command")] public string Command = "monument"; [JsonProperty(PropertyName = "Use GameTip for messages?")] public bool Use_GameTips = true; [JsonProperty(PropertyName = "Is it worth recreating boundaries upon detecting a wipe?")] public bool Wipe_Recreate = true; [JsonProperty(PropertyName = "List of tracked types of monuments. Leave blank to track all")] public HashSet Tracked_Types = new HashSet(); [JsonProperty(PropertyName = "Wipe ID")] public string WipeID = ""; public Oxide.Core.VersionNumber Version; } protected override void LoadConfig() { base.LoadConfig(); try { _config = Config.ReadObject(); if (_config == null) { PrintWarning("Configuration file not found. Creating a new one..."); LoadDefaultConfig(); } else if (_config.Version < Version) { PrintWarning($"Your configuration file version({_config.Version}) is outdated. Updating it to {Version}."); LoadDefaultConfig(); _config.Version = Version; PrintWarning($"The configuration file has been successfully updated to version {_config.Version}!"); } SaveConfig(); } catch { PrintError("Your configuration file contains an error. Using default configuration values."); LoadDefaultConfig(); } } protected override void SaveConfig() => Config.WriteObject(_config); protected override void LoadDefaultConfig() => _config = new Configuration(); #endregion #region ~Language~ protected override void LoadDefaultMessages() { lang.RegisterMessages(new Dictionary { ["MsgMonumentCreated"] = "Key {0} created at coordinates {1}. You need to specify the required data in the configuration file and reload the plugin.", ["MsgMonumentRecreated"] = "The boundaries of the monuments have been successfully recreated!", ["MsgMonumentCreateNoPosition"] = "You did not specify the position or specified it incorrectly!", ["MsgMonumentKeyNotFound"] = "Key {0} not found!", ["MsgMonumentNotFound"] = "{0} was not found!", ["MsgMonumentShow"] = "{0} is located at coordinates: {1}", ["MsgMonumentShowList"] = "Found and displayed {0} monuments with key {1}", ["MsgMonumentShowNoPlayer"] = "Monument display works only for players!", ["airfield_1"] = "Airfield", ["airfield_1_station"] = "Airfield Station", ["arctic_research_base_a"] = "Arctic Research Base", ["arctic_research_base_a_station"] = "Arctic Station", ["bandit_town"] = "Bandit Camp", ["bandit_town_station"] = "Bandit Station", ["compound"] = "Outpost", ["compound_station"] = "Outpost Station", ["desert_military_base_a"] = "Abandoned Military Base", ["desert_military_base_a_station"] = "Dune Station", ["desert_military_base_b"] = "Abandoned Military Base", ["desert_military_base_b_station"] = "Dune Station", ["desert_military_base_c"] = "Abandoned Military Base", ["desert_military_base_c_station"] = "Dune Station", ["desert_military_base_d"] = "Abandoned Military Base", ["desert_military_base_d_station"] = "Dune Station", ["excavator_1"] = "Giant Excavator Pit", ["excavator_1_station"] = "Excavator Station", ["ferry_terminal_1"] = "Ferry Terminal", ["ferry_terminal_1_station"] = "Ferry Terminal Station", ["fishing_village_a"] = "Large Fishing Village", ["fishing_village_a_station"] = "Large Fishing Station", ["fishing_village_b"] = "Fishing Village", ["fishing_village_b_station"] = "Fishing Station", ["fishing_village_c"] = "Fishing Village", ["fishing_village_c_station"] = "Fishing Station", ["gas_station_1"] = "Oxum's Gas Station", ["harbor_1"] = "Large Harbor", ["harbor_1_station"] = "Large Harbor Station", ["harbor_2"] = "Small Harbor", ["harbor_2_station"] = "Harbor Station", ["junkyard_1"] = "Junkyard", ["junkyard_1_station"] = "Junkyard Station", ["launch_site_1"] = "Launch Site", ["launch_site_1_station"] = "Launch Site Station", ["lighthouse"] = "Lighthouse", ["military_tunnel_1"] = "Military Tunnel", ["military_tunnel_1_station"] = "Military Tunnel Station", ["mining_quarry_a"] = "Sulfur Quarry", ["mining_quarry_b"] = "Stone Quarry", ["mining_quarry_c"] = "HQM Quarry", ["nuclear_missile_silo"] = "Missile Silo", ["nuclear_missile_silo_station"] = "Silo Station", ["oilrigai"] = "Oil Rig", ["oilrigai2"] = "Large Oil Rig", ["powerplant_1"] = "Power Plant", ["powerplant_1_station"] = "Power Plant Station", ["radtown_small_3"] = "Sewer Branch", ["radtown_small_3_station"] = "Sewer Branch Station", ["satellite_dish"] = "Satellite Dish", ["satellite_dish_station"] = "Satellite Station", ["sphere_tank"] = "The Dome", ["sphere_tank_station"] = "The Dome Station", ["stables_a"] = "Ranch", ["stables_a_station"] = "Ranch Station", ["stables_b"] = "Large Barn", ["stables_b_station"] = "Barn Station", ["station-sn-0"] = "Tunnel Station", ["station-sn-1"] = "Tunnel Station", ["station-sn-2"] = "Tunnel Station", ["station-sn-3"] = "Tunnel Station", ["station-we-0"] = "Tunnel Station", ["station-we-1"] = "Tunnel Station", ["station-we-2"] = "Tunnel Station", ["station-we-3"] = "Tunnel Station", ["supermarket_1"] = "Abandoned Supermarket", ["swamp_a"] = "Wild Swamp", ["swamp_b"] = "Wild Swamp", ["swamp_c"] = "Abandoned Cabins", ["trainyard_1"] = "Train Yard", ["trainyard_1_station"] = "Train Yard Station", ["underwater_lab_a"] = "Underwater Lab", ["underwater_lab_b"] = "Underwater Lab", ["underwater_lab_c"] = "Underwater Lab", ["underwater_lab_d"] = "Underwater Lab", ["warehouse"] = "Mining Outpost", ["water_treatment_plant_1"] = "Water Treatment Plant", ["water_treatment_plant_1_station"] = "Water Treatment Station", ["entrance_bunker_a"] = "Bunker Entrance", ["entrance_bunker_b"] = "Bunker Entrance", ["entrance_bunker_c"] = "Bunker Entrance", ["entrance_bunker_d"] = "Bunker Entrance", ["cave_small_easy"] = "Small Cave", ["cave_small_medium"] = "Medium Cave", ["cave_small_hard"] = "Medium Cave", ["cave_medium_easy"] = "Medium Cave", ["cave_medium_medium"] = "Medium Cave", ["cave_medium_hard"] = "Medium Cave", ["cave_large_medium"] = "Medium Cave", ["cave_large_hard"] = "Medium Cave", ["cave_large_sewers_hard"] = "Large Cave", ["ice_lake_1"] = "Ice Lake", ["ice_lake_2"] = "Ice Lake", ["ice_lake_3"] = "Large Ice Lake", ["ice_lake_4"] = "Small Ice Lake", ["power_sub_small_1"] = "Substation", ["power_sub_small_2"] = "Substation", ["power_sub_big_1"] = "Large Substation", ["power_sub_big_2"] = "Large Substation", ["water_well_a"] = "Water Well", ["water_well_b"] = "Water Well", ["water_well_c"] = "Water Well", ["water_well_d"] = "Water Well", ["water_well_e"] = "Water Well" }, this); lang.RegisterMessages(new Dictionary { ["MsgMonumentCreated"] = "Ключ {0} создан по координатам {1}. Вам необходимо в конфиг файле указать необходимые данные и перезагрузить плагин.", ["MsgMonumentRecreated"] = "Границы монументов успешно пересозданы!", ["MsgMonumentCreateNoPosition"] = "Вы не указали позицию, либо указали ее не правильно!", ["MsgMonumentKeyNotFound"] = "Ключ {0} не найден!", ["MsgMonumentNotFound"] = "{0} не найден!", ["MsgMonumentShow"] = "{0} расположен по координатам: {1}", ["MsgMonumentShowList"] = "Найдено и отображено {0} монументов с ключом {1}", ["MsgMonumentShowNoPlayer"] = "Отображение монументов работает только для игроков!", ["airfield_1"] = "Аэропорт", ["airfield_1_station"] = "Станция Аэропорт", ["arctic_research_base_a"] = "Арктическая база", ["arctic_research_base_a_station"] = "Станция Арктическая", ["bandit_town"] = "Лагерь бандитов", ["bandit_town_station"] = "Станция бандитов", ["compound"] = "Город", ["compound_station"] = "Станция Город", ["desert_military_base_a"] = "Заброшенная военная база", ["desert_military_base_a_station"] = "Станция Дюна", ["desert_military_base_b"] = "Заброшенная военная база", ["desert_military_base_b_station"] = "Станция Дюна", ["desert_military_base_c"] = "Заброшенная военная база", ["desert_military_base_c_station"] = "Станция Дюна", ["desert_military_base_d"] = "Заброшенная военная база", ["desert_military_base_d_station"] = "Станция Дюна", ["excavator_1"] = "Гигантский экскаватор", ["excavator_1_station"] = "Станция Экскаваторная", ["ferry_terminal_1"] = "Паромный терминал", ["ferry_terminal_1_station"] = "Станция Паромщиков", ["fishing_village_a"] = "Большая рыбацкая деревня", ["fishing_village_a_station"] = "Станция Рыбаков", ["fishing_village_b"] = "Рыбацкая деревня", ["fishing_village_b_station"] = "Станция Рыбаков", ["fishing_village_c"] = "Рыбацкая деревня", ["fishing_village_c_station"] = "Станция Рыбаков", ["gas_station_1"] = "Заправка", ["harbor_1"] = "Большой порт", ["harbor_1_station"] = "Станция Моряков", ["harbor_2"] = "Порт", ["harbor_2_station"] = "Станция Моряков", ["junkyard_1"] = "Свалка", ["junkyard_1_station"] = "Станция Мусорщиков", ["launch_site_1"] = "Космодром", ["launch_site_1_station"] = "Станция Космонавтов", ["lighthouse"] = "Маяк", ["military_tunnel_1"] = "Военные туннели", ["military_tunnel_1_station"] = "Станция Туннельная", ["mining_quarry_a"] = "Серный карьер", ["mining_quarry_b"] = "Каменный карьер", ["mining_quarry_c"] = "МВК карьер", ["nuclear_missile_silo"] = "Ракетная пусковая шахта", ["nuclear_missile_silo_station"] = "Станция Ракетная", ["oilrigai"] = "Нефтяная вышка", ["oilrigai2"] = "Большая нефтяная вышка", ["powerplant_1"] = "Электростанция", ["powerplant_1_station"] = "Станция Электриков", ["radtown_small_3"] = "Канализационный отвод", ["radtown_small_3_station"] = "Станция Отвод", ["satellite_dish"] = "Спутниковая тарелка", ["satellite_dish_station"] = "Станция Связистов", ["sphere_tank"] = "Сфера", ["sphere_tank_station"] = "Станция Сфера", ["stables_a"] = "Ранчо", ["stables_a_station"] = "Станция Ранчо", ["stables_b"] = "Большой амбар", ["stables_b_station"] = "Станция Амбарная", ["station-sn-0"] = "Станция метро", ["station-sn-1"] = "Станция метро", ["station-sn-2"] = "Станция метро", ["station-sn-3"] = "Станция метро", ["station-we-0"] = "Станция метро", ["station-we-1"] = "Станция метро", ["station-we-2"] = "Станция метро", ["station-we-3"] = "Станция метро", ["supermarket_1"] = "Супермаркет", ["swamp_a"] = "Болото", ["swamp_b"] = "Болото", ["swamp_c"] = "Заброшенные хижины", ["trainyard_1"] = "Железнодорожное депо", ["trainyard_1_station"] = "Станция Железнодорожников", ["underwater_lab_a"] = "Подводная лаборатория", ["underwater_lab_b"] = "Подводная лаборатория", ["underwater_lab_c"] = "Подводная лаборатория", ["underwater_lab_d"] = "Подводная лаборатория", ["warehouse"] = "Склад", ["water_treatment_plant_1"] = "Очистные сооружения", ["water_treatment_plant_1_station"] = "Станция Очистная", ["entrance_bunker_a"] = "Вход в бункер", ["entrance_bunker_b"] = "Вход в бункер", ["entrance_bunker_c"] = "Вход в бункер", ["entrance_bunker_d"] = "Вход в бункер", ["cave_small_easy"] = "Маленькая пещера", ["cave_small_medium"] = "Средняя пещера", ["cave_small_hard"] = "Средняя пещера", ["cave_medium_easy"] = "Средняя пещера", ["cave_medium_medium"] = "Средняя пещера", ["cave_medium_hard"] = "Средняя пещера", ["cave_large_medium"] = "Средняя пещера", ["cave_large_hard"] = "Средняя пещера", ["cave_large_sewers_hard"] = "Большая пещера", ["ice_lake_1"] = "Замерзшее озеро", ["ice_lake_2"] = "Замерзшее озеро", ["ice_lake_3"] = "Большое замерзшее озеро", ["ice_lake_4"] = "Маленькое замерзшее озеро", ["power_sub_small_1"] = "Подстанция", ["power_sub_small_2"] = "Подстанция", ["power_sub_big_1"] = "Большая подстанция", ["power_sub_big_2"] = "Большая подстанция", ["water_well_a"] = "Колодец с водой", ["water_well_b"] = "Колодец с водой", ["water_well_c"] = "Колодец с водой", ["water_well_d"] = "Колодец с водой", ["water_well_e"] = "Колодец с водой" }, this, "ru"); } #endregion #region ~Methods~ private void InitMonuments() { _monumentsBounds = _monumentsConfig.ReadObject>() ?? new Hash(); _playersInMonuments = new Hash(); _entitiesInMonuments = new Hash(); string monumentKey; string prefab; int miningoutpost = 0, lighthouse = 0, gasstation = 0, supermarket = 0, tunnel = 0, bunker = 0, cave = 0, icelake = 0, power = 0, waterwell = 0; foreach (var monument in TerrainMeta.Path.Monuments) { prefab = monument.name.ToLower(); monumentKey = ClearMonumentName(prefab); if (!_defaultBounds.ContainsKey(monumentKey)) continue; if (monument.IsSafeZone) { CreateWatcher(monumentKey, MonumentWatcher.MonumentType.SafeZone, monument.transform, prefab); continue; } switch (monumentKey) { case "oilrig_1": case "oilrig_2": case "underwater_lab_a": case "underwater_lab_b": case "underwater_lab_c": case "underwater_lab_d": CreateWatcher(monumentKey, MonumentWatcher.MonumentType.RadTownWater, monument.transform, prefab); break; case "lighthouse": lighthouse++; CreateWatcher(monumentKey, MonumentWatcher.MonumentType.RadTownSmall, monument.transform, prefab, $"{lighthouse}", $"#{lighthouse}"); break; case "gas_station_1": gasstation++; CreateWatcher(monumentKey, MonumentWatcher.MonumentType.RadTownSmall, monument.transform, prefab, $"{gasstation}", $"#{gasstation}"); break; case "supermarket_1": supermarket++; CreateWatcher(monumentKey, MonumentWatcher.MonumentType.RadTownSmall, monument.transform, prefab, $"{supermarket}", $"#{supermarket}"); break; case "warehouse": miningoutpost++; CreateWatcher(monumentKey, MonumentWatcher.MonumentType.RadTownSmall, monument.transform, prefab, $"{miningoutpost}", $"#{miningoutpost}"); break; case "mining_quarry_a": case "mining_quarry_b": case "mining_quarry_c": CreateWatcher(monumentKey, MonumentWatcher.MonumentType.MiningQuarry, monument.transform, prefab); break; case "swamp_a": case "swamp_b": case "swamp_c": CreateWatcher(monumentKey, MonumentWatcher.MonumentType.Swamp, monument.transform, prefab); break; case "entrance_bunker_a": case "entrance_bunker_b": case "entrance_bunker_c": case "entrance_bunker_d": bunker++; CreateWatcher(monumentKey, MonumentWatcher.MonumentType.BunkerEntrance, monument.transform, prefab, $"{bunker}", $"#{bunker}"); break; case "cave_small_easy": case "cave_small_medium": case "cave_small_hard": case "cave_medium_easy": case "cave_medium_medium": case "cave_medium_hard": case "cave_large_medium": case "cave_large_hard": case "cave_large_sewers_hard": cave++; CreateWatcher(monumentKey, MonumentWatcher.MonumentType.Cave, monument.transform, prefab, $"{cave}", $"#{cave}"); break; case "ice_lake_1": case "ice_lake_2": case "ice_lake_3": case "ice_lake_4": icelake++; CreateWatcher(monumentKey, MonumentWatcher.MonumentType.IceLake, monument.transform, prefab, $"{icelake}", $"#{icelake}"); break; case "power_sub_small_1": case "power_sub_small_2": case "power_sub_big_1": case "power_sub_big_2": power++; CreateWatcher(monumentKey, MonumentWatcher.MonumentType.PowerSubstation, monument.transform, prefab, $"{power}", $"#{power}"); break; case "water_well_a": case "water_well_b": case "water_well_c": case "water_well_d": case "water_well_e": waterwell++; CreateWatcher(monumentKey, MonumentWatcher.MonumentType.WaterWell, monument.transform, prefab, $"{waterwell}", $"#{waterwell}"); break; default: CreateWatcher(monumentKey, MonumentWatcher.MonumentType.RadTown, monument.transform, prefab); break; } } foreach (var station in TerrainMeta.Path.DungeonGridCells) { if (!station.name.Contains("/tunnel-station/station-", CompareOptions.IgnoreCase)) continue; prefab = station.name.ToLower(); monumentKey = ClearMonumentName(prefab); MonumentWatcher parentWatcher = null; var groundPos = new Vector3(station.transform.position.x, TerrainMeta.HeightMap.GetHeight(station.transform.position), station.transform.position.z); float distance = 100f; foreach (var monument in _monumentsList.Values) { if (monument.Properties.Type != MonumentWatcher.MonumentType.SafeZone && monument.Properties.Type != MonumentWatcher.MonumentType.RadTown) continue; if (Vector3.Distance(monument.collider.ClosestPointOnBounds(groundPos), groundPos) <= distance) parentWatcher = monument; } if (parentWatcher != null) { CreateWatcher(monumentKey, MonumentWatcher.MonumentType.TunnelStation, station.transform, prefab, parentWatcher.Properties.ID); _monumentsList[$"{monumentKey}_{parentWatcher.Properties.ID}"].Properties.LangKey = $"{parentWatcher.Properties.ID}_station"; } else { tunnel++; CreateWatcher(monumentKey, MonumentWatcher.MonumentType.TunnelStation, station.transform, prefab, $"{tunnel}", $"#{tunnel}"); } } SaveConfig(); _monumentsConfig.WriteObject(_monumentsBounds); _monumentsBounds.Clear(); } private void CreateWatcher(string monumentKey, MonumentWatcher.MonumentType type, Transform transform, string prefab, string idSuffix = "", string suffix = "") { if (_config.Tracked_Types.Any() && !_config.Tracked_Types.Contains($"{type}")) return; string monumentID = $"{monumentKey}{(!string.IsNullOrWhiteSpace(idSuffix) ? $"_{idSuffix}" : string.Empty)}"; if (!_monumentsBounds.ContainsKey(monumentID)) { _monumentsBounds[monumentID] = new MonumentBounds() { Center = transform.position, CenterOffset = _defaultBounds[monumentKey].CenterOffset, Size = _defaultBounds[monumentKey].Size, Rotation = transform.rotation.eulerAngles }; } var watcher = new GameObject().AddComponent(); watcher.InitializeMonument(new MonumentProperties(monumentID, type, prefab, monumentKey, suffix)); _monumentsList[monumentID] = watcher; } private void ClearWatchers() { Unsubscribe(nameof(OnEntityKill)); foreach (var kvp in _monumentsList) UnityEngine.Object.DestroyImmediate(kvp.Value.gameObject); _monumentsList.Clear(); _playersInMonuments = null; _entitiesInMonuments = null; } private void OnPlayerEnter(BasePlayer player, MonumentWatcher watcher) { _playersInMonuments[player.userID] = watcher; watcher.OnPlayerEnter(player); Interface.CallHook("OnEnterMonument", watcher.Properties.ID, player, $"{watcher.Properties.Type}"); } private void OnPlayerExit(BasePlayer player, MonumentWatcher watcher, string reason = "leave") { _playersInMonuments.Remove(player.userID); watcher.OnPlayerExit(player); Interface.CallHook("OnExitMonument", watcher.Properties.ID, player, $"{watcher.Properties.Type}", reason); } private void OnEntityEnter(BaseEntity entity, MonumentWatcher watcher) { _entitiesInMonuments[entity.net.ID] = watcher; watcher.OnEntityEnter(entity); Interface.CallHook("OnEnterMonument", watcher.Properties.ID, entity, $"{watcher.Properties.Type}"); } private void OnEntityExit(BaseEntity entity, MonumentWatcher watcher, string reason = "leave") { _entitiesInMonuments.Remove(entity.net.ID); watcher.OnEntityExit(entity); Interface.CallHook("OnExitMonument", watcher.Properties.ID, entity, $"{watcher.Properties.Type}", reason); } private static string ClearMonumentName(string prefabName) { prefabName = prefabName.Replace(".prefab", ""); string[] parts = prefabName.Split('/'); return parts[^1]; } private MonumentWatcher.MonumentType ConvertStringToEnum(string enumString) { if (Enum.TryParse(enumString, out MonumentWatcher.MonumentType parsedEnum)) return parsedEnum; PrintWarning($"Unable to convert to MonumentType: {enumString}"); return MonumentWatcher.MonumentType.RadTown; } private static Vector3 RotateVector(Vector3 vector, Vector3 rotation) => RotateVector(vector, Quaternion.Euler(rotation)); private static Vector3 RotateVector(Vector3 vector, Quaternion rotation) => rotation * vector; private Vector3[] GetCorners(MonumentWatcher watcher) { Vector3[] corners = new Vector3[8]; Vector3 center = watcher.Properties.Position; Quaternion rotation = Quaternion.Euler(watcher.Properties.Rotation); Vector3 size = watcher.Properties.Size / 2; corners[0] = center + rotation * new Vector3(size.x, size.y, size.z); corners[1] = center + rotation * new Vector3(size.x, -size.y, size.z); corners[2] = center + rotation * new Vector3(size.x, size.y, -size.z); corners[3] = center + rotation * new Vector3(size.x, -size.y, -size.z); corners[4] = center + rotation * new Vector3(-size.x, size.y, size.z); corners[5] = center + rotation * new Vector3(-size.x, -size.y, size.z); corners[6] = center + rotation * new Vector3(-size.x, size.y, -size.z); corners[7] = center + rotation * new Vector3(-size.x, -size.y, -size.z); return corners; } private Dictionary> GetLinesDictionary(Vector3[] corners) { Dictionary> linesDictionary = new Dictionary>(); for (int i = 0; i < corners.Length; i++) { Vector3 startPoint = corners[i]; List otherPoints = new List(corners); otherPoints.RemoveAt(i); linesDictionary.Add(startPoint, otherPoints); } return linesDictionary; } private bool StringToVector3(string value, out Vector3 result) { result = new Vector3(); if (!string.IsNullOrWhiteSpace(value)) { string[] xyzVal = value.Split(" "); if (xyzVal.Length >= 3) { float[] coords = new float[3]; int counter = 0; for (int i = 0; i < 3; i++) { if (float.TryParse(xyzVal[i], out float coord)) { coords[i] = coord; counter++; } } if (counter == 3) { result = new Vector3() { x = coords[0], y = coords[1], z = coords[2] }; return true; } } } return false; } private void UpdateFlag(BasePlayer player, BasePlayer.PlayerFlags flag, bool addFlag) { if (player != null) { player.SetPlayerFlag(flag, addFlag); player.SendNetworkUpdateImmediate(); } } private void SendMessage(IPlayer player, string replyKey, string[] replyArgs = null, bool isWarning = false) { if (player == null) return; if (replyArgs == null) replyArgs = new string[0]; if (!player.IsServer && _config.Use_GameTips) player.Command("gametip.showtoast", isWarning ? 1 : 0, string.Format(lang.GetMessage(replyKey, this, player.Id), replyArgs), null, null); else player.Reply(string.Format(lang.GetMessage(replyKey, this, player.Id), replyArgs)); } #endregion #region ~API~ private string GetMonumentDisplayName(string monumentID, ulong userID, bool showSuffix = true) => GetMonumentDisplayName(monumentID, $"{userID}", showSuffix); private string GetMonumentDisplayName(string monumentID, string userID = "", bool showSuffix = true) { if (userID == "0") userID = string.Empty; if (_monumentsList.TryGetValue(monumentID, out var watcher)) return $"{lang.GetMessage(watcher.Properties.LangKey, this, userID)}{(!string.IsNullOrWhiteSpace(watcher.Properties.Suffix) && showSuffix ? $" {watcher.Properties.Suffix}" : string.Empty)}"; return string.Empty; } private string GetMonumentType(string monumentID) => _monumentsList.TryGetValue(monumentID, out var watcher) ? $"{watcher.Properties.Type}" : string.Empty; private Vector3 GetMonumentPosition(string monumentID) => _monumentsList.TryGetValue(monumentID, out var watcher) ? watcher.Properties.Position : Vector3.zero; private string[] GetMonumentsList() => _monumentsList.Keys.ToArray(); private Dictionary GetMonumentsTypeDictionary() => _monumentsList.ToDictionary(monument => monument.Key, monument => $"{monument.Value.Properties.Type}"); private string[] GetMonumentsByType(string type) => _monumentsList.Where(monument => monument.Value.Properties.Type == ConvertStringToEnum(type)).Select(monument => monument.Key).ToArray(); private List GetMonumentPlayers(string monumentID) { if (_monumentsList.TryGetValue(monumentID, out var watcher)) return watcher.PlayersList; return new List(); } private List GetMonumentEntities(string monumentID) { if (_monumentsList.TryGetValue(monumentID, out var watcher)) return watcher.EntitiesList; return new List(); } private string GetPlayerMonument(ulong userID) { if (_playersInMonuments.TryGetValue(userID, out var watcher)) return watcher.Properties.ID; return string.Empty; } private string GetEntityMonument(BaseEntity entity) { if (entity != null && entity.net != null && _entitiesInMonuments.TryGetValue(entity.net.ID, out var watcher)) return watcher.Properties.ID; return string.Empty; } private string GetMonumentByPos(Vector3 pos) { foreach (var kvp in _monumentsList) { var watcher = kvp.Value; if (watcher.IsInBounds(pos)) return watcher.Properties.ID; } return string.Empty; } private bool IsPosInMonument(string monumentID, Vector3 pos) { if (_monumentsList.TryGetValue(monumentID, out var watcher)) return watcher.IsInBounds(pos); return false; } private bool IsPlayerInMounument(string monumentID, ulong userID) => IsPlayerInMounument(monumentID, BasePlayer.FindByID(userID)); private bool IsPlayerInMounument(string monumentID, BasePlayer player) { if (player != null && _monumentsList.TryGetValue(monumentID, out var watcher)) return watcher.PlayersList.Contains(player); return false; } private bool IsEntityInMounument(string monumentID, BaseEntity entity) { if (entity != null && _monumentsList.TryGetValue(monumentID, out var watcher)) return watcher.EntitiesList.Contains(entity); return false; } private void ShowBounds(string monumentID, BasePlayer player, float duration = 15f) { if (player != null && _monumentsList.TryGetValue(monumentID, out var watcher)) ShowBounds(watcher, player, duration); } private void ShowBounds(MonumentWatcher watcher, BasePlayer player, float duration = 15f) { if (watcher != null && player != null) { bool isAdmin = player.IsAdmin; try { if (!isAdmin) UpdateFlag(player, BasePlayer.PlayerFlags.IsAdmin, true); //TEXT player.SendConsoleCommand("ddraw.text", duration, Color.magenta, watcher.Properties.Position, watcher.Properties.ID); //CENTER player.SendConsoleCommand("ddraw.sphere", duration, Color.green, watcher.Properties.Position, 1f); //CORNERS var corners = GetCorners(watcher); foreach (var line in corners) { player.SendConsoleCommand("ddraw.sphere", duration, Color.red, line, 1f); } //LINES foreach (var line in GetLinesDictionary(corners)) { foreach (var endPos in line.Value) { player.SendConsoleCommand("ddraw.line", duration, Color.red, line.Key, endPos); } } } finally { if (!isAdmin) UpdateFlag(player, BasePlayer.PlayerFlags.IsAdmin, false); } } } #endregion #region ~Oxide Hooks~ void OnEntityKill(BasePlayer player) { if (_playersInMonuments.TryGetValue(player.userID, out var watcher)) OnPlayerExit(player, watcher, "death"); } void OnEntityKill(BaseEntity entity) { if (entity is null || !entity.IsValid()) return; if (_entitiesInMonuments.TryGetValue(entity.net.ID, out var watcher)) OnEntityExit(entity, watcher, "death"); } void Init() { Unsubscribe(nameof(OnEntityKill)); Instance = this; permission.RegisterPermission(PERMISSION_ADMIN, this); AddCovalenceCommand(_config.Command, nameof(MonumentsWatcher_Command)); } void OnServerInitialized() { if (string.IsNullOrWhiteSpace(_config.WipeID) || _config.WipeID != SaveRestore.WipeId) { _config.WipeID = SaveRestore.WipeId; if (_config.Wipe_Recreate) { Interface.Oxide.DataFileSystem.DeleteDataFile($"{Name}/MonumentsBounds"); PrintWarning("Wipe detected! Monument boundaries reset for proper creation of new boundaries."); } SaveConfig(); } _monumentsConfig = Interface.Oxide.DataFileSystem.GetFile($"{Name}/MonumentsBounds"); InitMonuments(); Subscribe(nameof(OnEntityKill)); } #endregion #region ~Commands~ private void MonumentsWatcher_Command(IPlayer player, string command, string[] args) { if (!player.IsServer && !permission.UserHasPermission(player.Id, PERMISSION_ADMIN)) return; if (args != null && args.Length > 0) { var bPlayer = player.Object as BasePlayer; string replyKey = string.Empty; string[] replyArgs = new string[5]; bool isWarning = false; /*if (args[0] == "create" && args.Length > 1) { string monumentKey = args[1]; if (!(args.Length > 2 && StringToVector3(args[2], out Vector3 pos))) pos = bPlayer?.transform.position ?? default; if (pos == default) { replyKey = "MsgMonumentCreateNoPosition"; isWarning = true; } else if (_defaultBounds.ContainsKey(monumentKey)) { string monumentID = monumentKey; var bounds = new MonumentBounds() { Center = pos, CenterOffset = _defaultBounds[monumentKey].CenterOffset, Size = _defaultBounds[monumentKey].Size, Rotation = default }; if (_config.MonumentsBounds.ContainsKey(monumentID)) { var matchingKeys = _config.MonumentsBounds.Keys.Where(key => key.StartsWith(monumentID + "_")).ToList(); monumentID += $"_{matchingKeys.Count + 1}"; } _config.MonumentsBounds[monumentID] = bounds; replyArgs[0] = monumentID; replyArgs[1] = $"{pos}"; replyKey = "MsgMonumentCreated"; } else { replyArgs[0] = monumentKey; replyKey = "MsgMonumentKeyNotFound"; isWarning = true; } } else if (args[0] == "recreate")*/ if (args[0] == "recreate") { ClearWatchers(); Interface.Oxide.DataFileSystem.DeleteDataFile($"{Name}/MonumentsBounds"); _monumentsConfig = Interface.Oxide.DataFileSystem.GetFile($"{Name}/MonumentsBounds"); InitMonuments(); Subscribe(nameof(OnEntityKill)); replyKey = "MsgMonumentRecreated"; } else if (args[0] == "show" && args.Length > 1) { if (bPlayer == null) { replyKey = "MsgMonumentShowNoPlayer"; isWarning = true; } else if (_monumentsList.TryGetValue(args[1], out var watcher)) { ShowBounds(watcher, bPlayer); replyArgs[0] = GetMonumentDisplayName(watcher.Properties.ID, player.Id); replyArgs[1] = $"{watcher.Properties.Position}"; replyKey = "MsgMonumentShow"; } else { var monumentsList = _monumentsList.Where(monument => monument.Value.Properties.LangKey == args[1]).Select(monument => monument.Value).ToList(); if (monumentsList.Any()) { foreach (var monumentWatcher in monumentsList) { ShowBounds(monumentWatcher, bPlayer); } replyArgs[0] = $"{monumentsList.Count}"; replyArgs[1] = args[1]; replyKey = "MsgMonumentShowList"; } else { replyArgs[0] = args[1]; replyKey = "MsgMonumentNotFound"; isWarning = true; } } } if (!string.IsNullOrWhiteSpace(replyKey)) SendMessage(player, replyKey, replyArgs, isWarning); } } #endregion #region ~Unload~ void Unload() { ClearWatchers(); Instance = null; _config = null; } #endregion #region ~MonumentData~ public class MonumentWatcher : MonoBehaviour { public enum MonumentType { SafeZone, RadTown, RadTownWater, RadTownSmall, TunnelStation, MiningQuarry, BunkerEntrance, Cave, Swamp, IceLake, PowerSubstation, WaterWell } public MonumentProperties Properties; public List PlayersList = Pool.GetList(); public List EntitiesList = Pool.GetList(); private Rigidbody rigidbody; public Collider collider; public Bounds colliderBounds; private void Awake() { gameObject.layer = (int)Layer.Reserved1; gameObject.name = "MonumentWatcher"; enabled = false; } private void OnDestroy() { ClearEntities(); Pool.FreeList(ref PlayersList); Pool.FreeList(ref EntitiesList); } private void ClearEntities() { if (Instance is null) return; for (int i = PlayersList.Count - 1; i >= 0; i--) { Instance.OnPlayerExit(PlayersList[i], this, "clearlist"); } for (int i = EntitiesList.Count - 1; i >= 0; i--) { Instance.OnEntityExit(EntitiesList[i], this, "clearlist"); } } public void InitializeMonument(MonumentProperties properties) { Properties = properties; transform.position = Properties.Position; transform.rotation = Quaternion.Euler(Properties.Rotation); InitializeCollider(); } public void Reset() { ClearEntities(); InitializeMonument(Properties); } private void InitializeCollider() { if (collider != null) DestroyImmediate(collider); if (rigidbody != null) DestroyImmediate(rigidbody); rigidbody = gameObject.AddComponent(); rigidbody.useGravity = false; rigidbody.isKinematic = true; rigidbody.detectCollisions = true; rigidbody.collisionDetectionMode = CollisionDetectionMode.Discrete; BoxCollider boxCollider = gameObject.GetComponent(); if (boxCollider is null) { boxCollider = gameObject.AddComponent(); boxCollider.isTrigger = true; } boxCollider.size = Properties.Size; colliderBounds = boxCollider.bounds; collider = boxCollider; } private void OnTriggerEnter(Collider collider) { if (collider is null || collider.gameObject is null || Instance is null) return; if (collider.gameObject.ToBaseEntity() is not BaseEntity entity || !entity.IsValid()) return; if (entity is BasePlayer player && !player.IsNpc) Instance.OnPlayerEnter(player, this); else Instance.OnEntityEnter(entity, this); } private void OnTriggerExit(Collider collider) { if (collider is null || collider.gameObject is null) return; if (collider.gameObject.ToBaseEntity() is not BaseEntity entity || !entity.IsValid()) return; if (entity is BasePlayer player && !player.IsNpc) Instance.OnPlayerExit(player, this); else Instance.OnEntityExit(entity, this); } public void OnPlayerEnter(BasePlayer player) { if (!PlayersList.Contains(player)) PlayersList.Add(player); } public void OnPlayerExit(BasePlayer player) { PlayersList.Remove(player); } public void OnEntityEnter(BaseEntity entity) { if (!EntitiesList.Contains(entity)) EntitiesList.Add(entity); } public void OnEntityExit(BaseEntity entity) { EntitiesList.Remove(entity); } public bool IsInBounds(Vector3 pos) => collider.bounds.Contains(pos); } public class BoundsValues { public Vector3 CenterOffset { get; set; } public Vector3 Size { get; set; } public BoundsValues(Vector3 offset, Vector3 size) { CenterOffset = offset; Size = size; } } public class MonumentBounds { public Vector3 Center { get; set; } public Vector3 CenterOffset { get; set; } public Vector3 Size { get; set; } public Vector3 Rotation { get; set; } } public class MonumentProperties { public string ID { get; set; } public MonumentWatcher.MonumentType Type { get; set; } public string Prefab { get; set; } public string LangKey { get; set; } public string Suffix { get; set; } public Vector3 Position { get; set; } public Vector3 Size { get; set; } public Vector3 Rotation { get; set; } public MonumentProperties(string id, MonumentWatcher.MonumentType type, string prefab, string langKey, string suffix = "") { ID = id; Type = type; Prefab = prefab; LangKey = langKey; Suffix = suffix; if (Instance._monumentsBounds.TryGetValue(id, out var bounds)) { //Position = bounds.Center + bounds.CenterOffset; Position = bounds.Center + RotateVector(bounds.CenterOffset, bounds.Rotation); Size = bounds.Size; Rotation = bounds.Rotation; } } } #endregion } }