Иконка ресурса

FREE MonumentChoice : 0.2.2 0.2.2

Нет прав для скачивания
C#:
using System;
using System.Collections.Generic;
using Newtonsoft.Json;
using Oxide.Core.Plugins;
using Oxide.Game.Rust.Cui;
using UnityEngine;

namespace Oxide.Plugins
{
    [Info("MonumentChoice", "RustFlash", "0.2.2")]
    [Description("Per-monument PvE/PvP runtime override system for Rust PvE servers")]
    public class MonumentChoice : RustPlugin
    {
        #region Fields

        [PluginReference] Plugin MyNotify;
        [PluginReference] Plugin ZoneManager;

        private const string PermUse = "monumentchoice.use";
        private const string PermBypass = "monumentchoice.bypass";
        private const string UI_MAIN = "MonumentChoice_UI";

        private Configuration config;

        private readonly Dictionary<string, MonumentState> states = new Dictionary<string, MonumentState>();
        private readonly Dictionary<string, MonumentData> monumentData = new Dictionary<string, MonumentData>();
        private readonly Dictionary<ulong, string> playerMonument = new Dictionary<ulong, string>();
        private readonly Dictionary<ulong, Dictionary<string, long>> cooldowns = new Dictionary<ulong, Dictionary<string, long>>();
        private readonly Dictionary<ulong, Dictionary<string, long>> travelerCooldowns = new Dictionary<ulong, Dictionary<string, long>>();

        private readonly Dictionary<MonumentInfo, string> monumentLookup = new Dictionary<MonumentInfo, string>();
        private readonly HashSet<string> activeZones = new HashSet<string>();

        private Timer positionTimer;
        private int preventBuildingMask;

        #endregion

        #region Config

        private class Configuration
        {
            [JsonProperty("DecisionCooldownMinutes")]
            public int DecisionCooldownMinutes = 15;

            [JsonProperty("TravelerCooldownMinutes")]
            public int TravelerCooldownMinutes = 5;

            [JsonProperty("UseMyNotify")]
            public bool UseMyNotify = false;

            [JsonProperty("ZoneManagerEnabled")]
            public bool ZoneManagerEnabled = false;

            [JsonProperty("ZoneRadiusPadding")]
            public float ZoneRadiusPadding = 0f;

            [JsonProperty("BlacklistedMonuments")]
            public List<string> BlacklistedMonuments = new List<string>
            {
                "compound",
                "bandit_town",
				"apartments_complex_1",
                "fishing_village_a",
                "fishing_village_b",
                "fishing_village_c",
                "large_fishing_village",
                "small_fishing_village",
                "stables_a",
                "stables_b",
                "ranch",
                "bandit_camp",

                "gas_station_1",
                "supermarket_1",
                "lighthouse",
                "mining_quarry_a",
                "mining_quarry_b",
                "mining_quarry_c",
                "substation_a",
                "substation_b",
                "substation_c",
                "swamp_a",
                "swamp_b",
                "swamp_c",

                "ice_lake_1",
                "ice_lake_2",
                "ice_lake_3",
                "ice_lake_4",

                "power_sub_big_1",
                "power_sub_big_2",
                "power_sub_small_1",
                "power_sub_small_2",
                "ferryman",

                "water_well_a",
                "water_well_b",
                "water_well_c",
                "water_well_d",
                "water_well_e",

                "cave_small_easy",
                "cave_small_medium",
                "cave_small_hard",
                "cave_medium_easy",
                "cave_medium_medium",
                "cave_medium_hard",
                "cave_large_easy",
                "cave_large_medium",
                "cave_large_hard",
                "cave_large_sewers_hard",

                "excavator_1",
                "nuclear_missile_silo",
                "arctic_research_base_a",
                "desert_military_base_a",
                "desert_military_base_b",
                "desert_military_base_c",
                "desert_military_base_d"
            };
        }

        protected override void LoadConfig()
        {
            base.LoadConfig();
            try
            {
                config = Config.ReadObject<Configuration>();
                if (config == null) LoadDefaultConfig();
            }
            catch { LoadDefaultConfig(); }
            SaveConfig();
        }

        protected override void LoadDefaultConfig() => config = new Configuration();
        protected override void SaveConfig() => Config.WriteObject(config);

        #endregion

        #region Data Models

        private enum MonumentMode { Unclaimed, PvE, PvP }

        private class MonumentState
        {
            public string MonumentId;
            public string DisplayName;
            public MonumentMode Mode = MonumentMode.Unclaimed;
            public ulong OwnerId;
            public string OwnerName;
        }

        private class MonumentData
        {
            public MonumentInfo Monument;
        }

        #endregion

        #region Monument Display Names

        private static readonly Dictionary<string, string> DisplayNames = new Dictionary<string, string>
        {
            ["launch_site"] = "Launch Site",
            ["military_tunnel_1"] = "Military Tunnel",
            ["airfield_1"] = "Airfield",
            ["powerplant_1"] = "Power Plant",
            ["trainyard_1"] = "Train Yard",
            ["water_treatment_plant_1"] = "Water Treatment Plant",
            ["satellite_dish_1"] = "Satellite Dish",
            ["lighthouse"] = "Lighthouse",
            ["harbor_1"] = "Harbor",
            ["harbor_2"] = "Harbor",
            ["junkyard_1"] = "Junkyard",
            ["gas_station_1"] = "Gas Station",
            ["supermarket_1"] = "Supermarket",
            ["mining_quarry_a"] = "Sulfur Quarry",
            ["mining_quarry_b"] = "HQM Quarry",
            ["mining_quarry_c"] = "Stone Quarry",
            ["excavator_1"] = "Giant Excavator",
            ["radtown_small_3"] = "Sewer Branch",
            ["sphere_tank"] = "Dome",
            ["nuclear_missile_silo"] = "Missile Silo",
            ["oilrig_1"] = "Small Oil Rig",
            ["oilrig_2"] = "Large Oil Rig",
            ["substation_a"] = "Power Substation",
            ["substation_b"] = "Power Substation",
            ["substation_c"] = "Power Substation",
            ["arctic_research_base_a"] = "Arctic Research Base",
            ["desert_military_base_a"] = "Desert Military Base",
            ["desert_military_base_b"] = "Desert Military Base",
            ["desert_military_base_c"] = "Desert Military Base",
            ["desert_military_base_d"] = "Desert Military Base",
            ["ferryman"] = "Ferry Terminal",
            ["underwater_lab_a"] = "Underwater Lab",
            ["underwater_lab_b"] = "Underwater Lab",
            ["underwater_lab_c"] = "Underwater Lab",
            ["underwater_lab_d"] = "Underwater Lab",
        };

        #endregion

        #region Init / Unload

        private void Init()
        {
            permission.RegisterPermission(PermUse, this);
            permission.RegisterPermission(PermBypass, this);
            RegisterLanguages();
        }

        private void OnServerInitialized()
        {
            preventBuildingMask = LayerMask.GetMask("Prevent Building");
            RegisterMonuments();
            positionTimer = timer.Every(1f, CheckPlayerPositions);
        }

        private void Unload()
        {
            positionTimer?.Destroy();

            RemoveAllPvPZones();

            foreach (var player in BasePlayer.activePlayerList)
                CuiHelper.DestroyUi(player, UI_MAIN);
        }

        #endregion

        #region Monument Registration

        private void RegisterMonuments()
        {
            if (TerrainMeta.Path?.Monuments == null)
            {
                PrintWarning("No monuments found on this map.");
                return;
            }

            var idCounts = new Dictionary<string, int>();

            foreach (var monument in TerrainMeta.Path.Monuments)
            {
                string baseId = ExtractMonumentId(monument.name);
                if (string.IsNullOrEmpty(baseId)) continue;
                if (IsBlacklisted(baseId)) continue;

                idCounts.TryGetValue(baseId, out int count);
                string monumentId = count == 0 ? baseId : $"{baseId}_{count}";
                idCounts[baseId] = count + 1;

                monumentData[monumentId] = new MonumentData
                {
                    Monument = monument
                };

                monumentLookup[monument] = monumentId;

                states[monumentId] = new MonumentState
                {
                    MonumentId = monumentId,
                    DisplayName = ResolveDisplayName(baseId, count)
                };
            }

            Puts($"MonumentChoice: {states.Count} monuments registered.");
        }

        private string ExtractMonumentId(string prefabPath)
        {
            if (string.IsNullOrEmpty(prefabPath)) return null;
            var parts = prefabPath.Split('/');
            return parts[parts.Length - 1].Replace(".prefab", "").Trim();
        }

        private string ResolveDisplayName(string baseId, int duplicateIndex)
        {
            string name = DisplayNames.TryGetValue(baseId, out string display)
                ? display
                : System.Text.RegularExpressions.Regex.Replace(
                      baseId.Replace("_", " "), @"\s*\d+\s*$", "").Trim();

            if (duplicateIndex > 0)
                name += $" {(char)('A' + duplicateIndex)}";

            return name;
        }

        private bool IsBlacklisted(string id)
        {
            foreach (var entry in config.BlacklistedMonuments)
                if (string.Equals(id, entry, StringComparison.OrdinalIgnoreCase)) return true;
            return false;
        }

        #endregion

        #region Position Tracking (1-second timer)

        private void CheckPlayerPositions()
        {
            foreach (var player in BasePlayer.activePlayerList)
            {
                if (player == null || !player.IsConnected) continue;

                string newMonument = FindMonumentAt(player.transform.position);

                playerMonument.TryGetValue(player.userID, out string oldMonument);

                if (newMonument == oldMonument) continue;

                if (oldMonument != null)
                    HandlePlayerExit(player, oldMonument);

                if (newMonument != null)
                    HandlePlayerEnter(player, newMonument);
                else
                    playerMonument.Remove(player.userID);
            }
        }

        private string FindMonumentAt(Vector3 pos)
        {
            List<Collider> hits = Facepunch.Pool.Get<List<Collider>>();
            Vis.Colliders(pos, 0.5f, hits, preventBuildingMask, QueryTriggerInteraction.Collide);

            string found = null;
            foreach (var col in hits)
            {
                var tag = col.GetComponentInParent<PreventBuildingMonumentTag>();
                if (tag == null) continue;

                var monument = tag.GetAttachedMonument();
                if (monument != null && monumentLookup.TryGetValue(monument, out string id))
                {
                    found = id;
                    break;
                }
            }

            Facepunch.Pool.FreeUnmanaged<Collider>(ref hits);

            if (found != null) return found;

            foreach (var kvp in monumentData)
                if (kvp.Value.Monument.IsInBounds(pos))
                    return kvp.Key;

            return null;
        }

        #endregion

        #region Enter / Exit Logic

        private void HandlePlayerEnter(BasePlayer player, string monumentId)
        {
            playerMonument[player.userID] = monumentId;

            if (!states.TryGetValue(monumentId, out var state)) return;

            if (HasBypass(player)) return;

            if (!permission.UserHasPermission(player.UserIDString, PermUse)) return;

            if (state.Mode == MonumentMode.Unclaimed)
            {
                if (IsTravelerSuppressed(player.userID, monumentId))
                    return;

                if (IsOnCooldown(player.userID, monumentId))
                {
                    SendMessage(player, Lang("Cooldown", player.UserIDString));
                    return;
                }
                ShowSelectionUI(player, state);
            }
            else
            {
                SendModeInfo(player, state);
            }
        }

        private void HandlePlayerExit(BasePlayer player, string monumentId)
        {
            playerMonument.Remove(player.userID);
            CuiHelper.DestroyUi(player, UI_MAIN);

            if (!states.TryGetValue(monumentId, out var state)) return;
            if (state.OwnerId != player.userID) return;

            string playerName = player.displayName;
            string displayName = state.DisplayName;

            BroadcastToMonument(monumentId, Lang("LeftZone", null, playerName, displayName));
            BroadcastToMonument(monumentId, Lang("ZoneAvailable", null, displayName));

            SendMessage(player, Lang("LeftZone", player.UserIDString, playerName, displayName));
            SendMessage(player, Lang("ZoneAvailable", player.UserIDString, displayName));

            SetCooldown(player.userID, monumentId);
            ResetMonument(state);
        }

        private void ResetMonument(MonumentState state)
        {
            RemovePvPZone(state.MonumentId);

            state.Mode = MonumentMode.Unclaimed;
            state.OwnerId = 0;
            state.OwnerName = string.Empty;
        }

        #endregion

        #region Selection UI

        private void ShowSelectionUI(BasePlayer player, MonumentState state)
        {
            CuiHelper.DestroyUi(player, UI_MAIN);

            var container = new CuiElementContainer();

            container.Add(new CuiPanel
            {
                Image = { Color = "0 0 0 0.88" },
                RectTransform = { AnchorMin = "0.34 0.33", AnchorMax = "0.66 0.65" },
                CursorEnabled = true
            }, "Hud", UI_MAIN);

            container.Add(new CuiLabel
            {
                Text = {
                    Text = state.DisplayName,
                    FontSize = 18,
                    Align = TextAnchor.MiddleCenter,
                    Color = "1 0.85 0.2 1"
                },
                RectTransform = { AnchorMin = "0.05 0.80", AnchorMax = "0.95 0.97" }
            }, UI_MAIN);

            container.Add(new CuiLabel
            {
                Text = {
                    Text = "Choose your mode:",
                    FontSize = 11,
                    Align = TextAnchor.MiddleCenter,
                    Color = "0.75 0.75 0.75 1"
                },
                RectTransform = { AnchorMin = "0.05 0.66", AnchorMax = "0.95 0.80" }
            }, UI_MAIN);

            container.Add(new CuiButton
            {
                Button = {
                    Command = $"monumentchoice.select pve {state.MonumentId}",
                    Color = "0.13 0.53 0.13 0.95"
                },
                RectTransform = { AnchorMin = "0.05 0.30", AnchorMax = "0.475 0.63" },
                Text = {
                    Text = "PvE",
                    FontSize = 24,
                    Align = TextAnchor.MiddleCenter,
                    Color = "0.85 1 0.85 1"
                }
            }, UI_MAIN);

            container.Add(new CuiButton
            {
                Button = {
                    Command = $"monumentchoice.select pvp {state.MonumentId}",
                    Color = "0.62 0.1 0.1 0.95"
                },
                RectTransform = { AnchorMin = "0.525 0.30", AnchorMax = "0.95 0.63" },
                Text = {
                    Text = "PvP",
                    FontSize = 24,
                    Align = TextAnchor.MiddleCenter,
                    Color = "1 0.82 0.82 1"
                }
            }, UI_MAIN);

            container.Add(new CuiButton
            {
                Button = {
                    Command = $"monumentchoice.skip {state.MonumentId}",
                    Color = "0.40 0.62 0.82 0.85"
                },
                RectTransform = { AnchorMin = "0.22 0.07", AnchorMax = "0.78 0.25" },
                Text = {
                    Text = "I'm just a traveler",
                    FontSize = 12,
                    Align = TextAnchor.MiddleCenter,
                    Color = "0.92 0.97 1 1"
                }
            }, UI_MAIN);

            CuiHelper.AddUi(player, container);
        }

        [ConsoleCommand("monumentchoice.select")]
        private void CmdSelectMode(ConsoleSystem.Arg arg)
        {
            var player = arg.Player();
            if (player == null) return;
            if (!arg.HasArgs(2)) return;

            string modeStr = arg.GetString(0).ToLower();
            string monumentId = arg.GetString(1);

            CuiHelper.DestroyUi(player, UI_MAIN);

            if (!states.TryGetValue(monumentId, out var state)) return;
            if (state.Mode != MonumentMode.Unclaimed) return;
            if (!permission.UserHasPermission(player.UserIDString, PermUse)) return;

            if (IsOnCooldown(player.userID, monumentId))
            {
                SendMessage(player, Lang("Cooldown", player.UserIDString));
                return;
            }

            state.Mode = modeStr == "pvp" ? MonumentMode.PvP : MonumentMode.PvE;
            state.OwnerId = player.userID;
            state.OwnerName = player.displayName;

            if (state.Mode == MonumentMode.PvP)
                CreatePvPZone(monumentId, state.DisplayName);

            string key = state.Mode == MonumentMode.PvP ? "SelectedPvP" : "SelectedPvE";
            BroadcastToMonument(monumentId, Lang(key, null, player.displayName, state.DisplayName));
        }

        [ConsoleCommand("monumentchoice.skip")]
        private void CmdSkipChoice(ConsoleSystem.Arg arg)
        {
            var player = arg.Player();
            if (player == null) return;
            if (!arg.HasArgs(1)) return;

            string monumentId = arg.GetString(0);

            CuiHelper.DestroyUi(player, UI_MAIN);

            if (!states.TryGetValue(monumentId, out var state)) return;
            if (state.Mode != MonumentMode.Unclaimed) return;

            SetTravelerCooldown(player.userID, monumentId);
        }

        #endregion

        #region Gameplay Hooks

        private object CanEntityTakeDamage(BasePlayer victim, HitInfo info)
        {
            if (victim == null || info == null) return null;

            var attacker = info.InitiatorPlayer;
            if (attacker == null || attacker == victim) return null;
            if (HasBypass(victim) || HasBypass(attacker)) return null;

            if (!InSamePvPMonument(attacker.userID, victim.userID)) return null;

            return true;
        }

        private object CanTeleport(BasePlayer player)
        {
            if (player == null) return null;

            playerMonument.TryGetValue(player.userID, out string monumentId);
            if (monumentId == null) return null;

            if (!states.TryGetValue(monumentId, out var state)) return null;
            if (state.Mode != MonumentMode.PvP) return null;

            return false;
        }

        #endregion

        #region Disconnect / Reconnect

        private void OnPlayerDisconnected(BasePlayer player, string reason)
        {
            playerMonument.TryGetValue(player.userID, out string monumentId);
            if (monumentId != null)
                HandlePlayerExit(player, monumentId);
        }

        #endregion

        #region Public API (callable by other plugins)

        private bool IsPlayerInPvPZone(BasePlayer player)
        {
            if (player == null) return false;
            playerMonument.TryGetValue(player.userID, out string monumentId);
            if (monumentId == null) return false;
            return states.TryGetValue(monumentId, out var state) && state.Mode == MonumentMode.PvP;
        }

        private string GetPlayerMonumentMode(BasePlayer player)
        {
            if (player == null) return "Unclaimed";
            playerMonument.TryGetValue(player.userID, out string monumentId);
            if (monumentId == null) return "Unclaimed";
            if (!states.TryGetValue(monumentId, out var state)) return "Unclaimed";
            return state.Mode.ToString();
        }

        #endregion

        #region ZoneManager Integration

        private void CreatePvPZone(string monumentId, string displayName)
        {
            if (!config.ZoneManagerEnabled || ZoneManager == null) return;
            if (!monumentData.TryGetValue(monumentId, out var data) || data.Monument == null) return;

            MonumentInfo monument = data.Monument;
            Vector3 center = monument.transform.TransformPoint(monument.Bounds.center);
            float radius = ZoneRadiusFor(monument);

            string zoneId = ZoneId(monumentId);
            string[] args = { "name", "PvP " + displayName, "radius", radius.ToString(System.Globalization.CultureInfo.InvariantCulture) };

            ZoneManager.Call("CreateOrUpdateZone", zoneId, args, center);
            activeZones.Add(zoneId);
        }

        private void RemovePvPZone(string monumentId)
        {
            if (ZoneManager == null) return;

            string zoneId = ZoneId(monumentId);
            if (!activeZones.Remove(zoneId)) return;

            ZoneManager.Call("EraseZone", zoneId);
        }

        private void RemoveAllPvPZones()
        {
            if (ZoneManager == null)
            {
                activeZones.Clear();
                return;
            }

            foreach (var zoneId in activeZones)
                ZoneManager.Call("EraseZone", zoneId);

            activeZones.Clear();
        }

        private float ZoneRadiusFor(MonumentInfo monument)
        {
            float ex = monument.Bounds.extents.x;
            float ez = monument.Bounds.extents.z;
            float radius = Mathf.Sqrt(ex * ex + ez * ez) + config.ZoneRadiusPadding;
            return radius < 10f ? 10f : radius;
        }

        private string ZoneId(string monumentId) => "monumentchoice_" + monumentId;

        #endregion

        #region Helpers

        private bool InSamePvPMonument(ulong playerAId, ulong playerBId)
        {
            playerMonument.TryGetValue(playerAId, out string monA);
            playerMonument.TryGetValue(playerBId, out string monB);

            if (monA == null || monA != monB) return false;

            return states.TryGetValue(monA, out var state) && state.Mode == MonumentMode.PvP;
        }

        private bool HasBypass(BasePlayer player) =>
            permission.UserHasPermission(player.UserIDString, PermBypass);

        private bool IsOnCooldown(ulong playerId, string monumentId)
        {
            if (!cooldowns.TryGetValue(playerId, out var dict)) return false;
            if (!dict.TryGetValue(monumentId, out long expiry)) return false;
            return DateTimeOffset.UtcNow.ToUnixTimeSeconds() < expiry;
        }

        private void SetCooldown(ulong playerId, string monumentId)
        {
            if (!cooldowns.ContainsKey(playerId))
                cooldowns[playerId] = new Dictionary<string, long>();

            cooldowns[playerId][monumentId] =
                DateTimeOffset.UtcNow.ToUnixTimeSeconds() + config.DecisionCooldownMinutes * 60L;
        }

        private bool IsTravelerSuppressed(ulong playerId, string monumentId)
        {
            if (!travelerCooldowns.TryGetValue(playerId, out var dict)) return false;
            if (!dict.TryGetValue(monumentId, out long expiry)) return false;
            return DateTimeOffset.UtcNow.ToUnixTimeSeconds() < expiry;
        }

        private void SetTravelerCooldown(ulong playerId, string monumentId)
        {
            if (!travelerCooldowns.ContainsKey(playerId))
                travelerCooldowns[playerId] = new Dictionary<string, long>();

            travelerCooldowns[playerId][monumentId] =
                DateTimeOffset.UtcNow.ToUnixTimeSeconds() + config.TravelerCooldownMinutes * 60L;
        }

        private void BroadcastToMonument(string monumentId, string message)
        {
            foreach (var player in BasePlayer.activePlayerList)
            {
                playerMonument.TryGetValue(player.userID, out string mon);
                if (mon == monumentId)
                    SendMessage(player, message);
            }
        }

        private void SendMessage(BasePlayer player, string message)
        {
            if (config.UseMyNotify && MyNotify != null)
                MyNotify.Call("SendNotify", player, message);
            else
                player.ChatMessage($"<color=#e8c840>[Monument Choice]</color> {message}");
        }

        private void SendModeInfo(BasePlayer player, MonumentState state)
        {
            string key = state.Mode == MonumentMode.PvP ? "ModePvP" : "ModePvE";
            SendMessage(player, Lang(key, player.UserIDString));
        }

        #endregion

        #region Lang

        private void RegisterLanguages()
        {
            lang.RegisterMessages(new Dictionary<string, string>
            {
                ["ModePvE"] = "This monument is currently <color=#55ffaa>PvE</color>.",
                ["ModePvP"] = "This monument is currently <color=#ff5555>PvP</color>.",
                ["SelectedPvE"] = "{0} selected <color=#55ffaa>PvE</color> for {1}.",
                ["SelectedPvP"] = "{0} selected <color=#ff5555>PvP</color> for {1}.",
                ["LeftZone"] = "{0} left {1}.",
                ["ZoneAvailable"] = "{0} is now available again.",
                ["Cooldown"] = "You must wait before choosing this monument again."
            }, this, "en");

            lang.RegisterMessages(new Dictionary<string, string>
            {
                ["ModePvE"] = "In diesem Monument ist aktuell <color=#55ffaa>PvE</color> aktiv.",
                ["ModePvP"] = "In diesem Monument ist aktuell <color=#ff5555>PvP</color> aktiv.",
                ["SelectedPvE"] = "{0} hat <color=#55ffaa>PvE</color> für {1} aktiviert.",
                ["SelectedPvP"] = "{0} hat <color=#ff5555>PvP</color> für {1} aktiviert.",
                ["LeftZone"] = "{0} hat {1} verlassen.",
                ["ZoneAvailable"] = "{0} steht nun wieder zur Verfügung.",
                ["Cooldown"] = "Du musst warten, bevor du dieses Monument erneut auswählen darfst."
            }, this, "de");
        }

        private string Lang(string key, string userId = null, params object[] args)
        {
            string msg = lang.GetMessage(key, this, userId);
            return args.Length > 0 ? string.Format(msg, args) : msg;
        }

        #endregion
    }
}

Автор
15FPS
Скачиваний
0
Просмотры
1
Первый выпуск
Обновление
Оценка
0.00 звёзд 0 оценок
Сверху