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

FREE HoodWars : 7.9.6 7.9.6

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

namespace Oxide.Plugins
{
    [Info("HoodWars", "Gemini", "7.9.6")]
    [Description("A robust, comprehensive gang-based territory and identity system for a unique vanilla-feel Rust experience.")]
    public class HoodWars : RustPlugin
    {
        #region References & Fields

        private static HoodWars _instance;
        private StoredData _storedData;
        private DynamicConfigFile _data;

        private Dictionary<ulong, float> _spottedPlayers = new Dictionary<ulong, float>();
        private Dictionary<NetworkableId, MapMarkerGenericRadius> _activeMarkers = new Dictionary<NetworkableId, MapMarkerGenericRadius>();
        private Dictionary<ulong, Dictionary<NeighborhoodType, float>> _trespassWarningCooldowns = new Dictionary<ulong, Dictionary<NeighborhoodType, float>>();
        private Dictionary<NeighborhoodType, NetworkableId> _hqToolCupboards = new Dictionary<NeighborhoodType, NetworkableId>();
        private Dictionary<NeighborhoodType, SphereEntity> _hqSphereMarkers = new Dictionary<NeighborhoodType, SphereEntity>();
        
        // Timer for periodic updates
        private Timer _identityTimer;

        // ManualDoor plugin reference for hotel door integration
        [PluginReference]
        private Plugin ManualDoor;

        // GangKits plugin reference for gang outfit/weapon integration
        [PluginReference]
        private Plugin GangKits;

        // DriveBySedanGangs plugin reference for NPC drive-by events
        [PluginReference]
        private Plugin DriveBySedanGangs;

        private const string PrefabMarker = "assets/prefabs/tools/map/genericradiusmarker.prefab";
        private const string PrefabSphere = "assets/prefabs/visualization/sphere.prefab";
        private const string PrefabToolCupboard = "assets/prefabs/deployable/tool cupboard/cupboard.tool.deployed.prefab";
        private const string PermAdmin = "hoodwars.admin";
        private const string PermUse = "hoodwars.use";

        #endregion

        #region Configuration

        private ConfigData _config;

        private class ConfigData
        {
            [JsonProperty("General Settings")]
            public GeneralSettings General { get; set; }

            [JsonProperty("Neighborhood Definitions")]
            public List<NeighborhoodConfig> Neighborhoods { get; set; }

            [JsonProperty("Marker Settings")]
            public MarkerSettings Markers { get; set; }

            [JsonProperty("Chat Settings")]
            public ChatSettings Chat { get; set; }

            [JsonProperty("HQ Settings")]
            public HQSettings HQ { get; set; }

            public class GeneralSettings
            {
                [JsonProperty("Identity Reveal Duration (Seconds)")]
                public float RevealDuration { get; set; } = 300f;

                [JsonProperty("Proximity Reveal Distance (Meters)")]
                public float ProximityDistance { get; set; } = 5f;

                [JsonProperty("Kill Reveal (True/False)")]
                public bool KillReveal { get; set; } = true;

                [JsonProperty("Scrap Bounty for Rat TCs")]
                public int BountyAmount { get; set; } = 150;

                [JsonProperty("Enable Reputation System")]
                public bool UseReputation { get; set; } = true;
            }

            public class NeighborhoodConfig
            {
                public string Name { get; set; }
                public NeighborhoodType Type { get; set; }
                public float MinX { get; set; }
                public float MaxX { get; set; }
                public float MinZ { get; set; }
                public float MaxZ { get; set; }
                public string HexColor { get; set; }

                [JsonProperty("HQ Center X")]
                public float HQCenterX { get; set; }

                [JsonProperty("HQ Center Z")]
                public float HQCenterZ { get; set; }

                [JsonProperty("HQ Radius (Meters)")]
                public float HQRadius { get; set; } = 50f;
            }

            public class HQSettings
            {
                [JsonProperty("Enable HQ Safezones")]
                public bool EnableHQSafezones { get; set; } = true;

                [JsonProperty("Trespass Warning Interval (Seconds)")]
                public float TrespassWarningInterval { get; set; } = 30f;

                [JsonProperty("Show HQ Zone Spheres")]
                public bool ShowHQSpheres { get; set; } = true;

                [JsonProperty("HQ Sphere Opacity (0.0 to 1.0)")]
                public float HQSphereAlpha { get; set; } = 0.25f;

                [JsonProperty("Allowed Hotel Items (Short Prefab Names)")]
                public List<string> AllowedHotelItems { get; set; } = new List<string>
                {
                    "box.wooden.large",
                    "box.wooden",
                    "sleepingbag_leather_deployed",
                    "bed_deployed",
                    "small_stash_deployed",
                    "rug.deployed",
                    "rug.bear.deployed",
                    "furnace",
                    "campfire",
                    "workbench1.deployed",
                    "research.table.deployed",
                    "mixingtable.deployed",
                    "locker.deployed",
                    "fridge.deployed",
                    "repairbench_deployed"
                };
            }

            public class MarkerSettings
            {
                [JsonProperty("Marker Opacity (0.0 to 1.0)")]
                public float InfiltratorAlpha { get; set; } = 0.3f;

                [JsonProperty("Marker Size (Radius)")]
                public float InfiltratorRadius { get; set; } = 1.2f;

                [JsonProperty("Position Jitter (Randomize Offset Radius)")]
                public float RandomOffsetRadius { get; set; } = 25f;

                public string Color1 { get; set; } = "#FF0000";
                public string Color2 { get; set; } = "#000000";
            }

            public class ChatSettings
            {
                public string AnonymousColor { get; set; } = "#55aaee";
                public string RevealedColor { get; set; } = "#ff4444";
                public string NeighborhoodColor { get; set; } = "#ffffff";
                public string Prefix { get; set; } = "STREETS";
            }
        }

        protected override void LoadConfig()
        {
            base.LoadConfig();
            try
            {
                _config = Config.ReadObject<ConfigData>();
                if (_config == null) throw new Exception();
                
                // Ensure nested objects exist (for existing configs that may not have new properties)
                if (_config.General == null) _config.General = new ConfigData.GeneralSettings();
                if (_config.Markers == null) _config.Markers = new ConfigData.MarkerSettings();
                if (_config.Chat == null) _config.Chat = new ConfigData.ChatSettings();
                if (_config.HQ == null) _config.HQ = new ConfigData.HQSettings();
                if (_config.Neighborhoods == null) _config.Neighborhoods = new List<ConfigData.NeighborhoodConfig>();
                if (_config.HQ.AllowedHotelItems == null) _config.HQ.AllowedHotelItems = new List<string> 
                {
                    "box.wooden.large", "box.wooden", "sleepingbag_leather_deployed", 
                    "bed_deployed", "small_stash_deployed", "furnace", "campfire",
                    "workbench1.deployed", "locker.deployed", "fridge.deployed"
                };
            }
            catch
            {
                LoadDefaultConfig();
            }
            SaveConfig();
        }

        protected override void LoadDefaultConfig()
        {
            // For a 2100 map, coordinates range from -1050 to 1050
            // HQ centers are in the middle of each quadrant
            _config = new ConfigData
            {
                General = new ConfigData.GeneralSettings(),
                Markers = new ConfigData.MarkerSettings(),
                Chat = new ConfigData.ChatSettings(),
                HQ = new ConfigData.HQSettings(),
                Neighborhoods = new List<ConfigData.NeighborhoodConfig>
                {
                    new ConfigData.NeighborhoodConfig { Name = "Westside Pirus", Type = NeighborhoodType.West, MinX = -1050, MaxX = 0, MinZ = 0, MaxZ = 1050, HexColor = "#ff4444", HQCenterX = -525, HQCenterZ = 525, HQRadius = 50 },
                    new ConfigData.NeighborhoodConfig { Name = "Northside Vagos", Type = NeighborhoodType.North, MinX = 0, MaxX = 1050, MinZ = 0, MaxZ = 1050, HexColor = "#ccff33", HQCenterX = 525, HQCenterZ = 525, HQRadius = 50 },
                    new ConfigData.NeighborhoodConfig { Name = "Southside Sureños", Type = NeighborhoodType.South, MinX = -1050, MaxX = 0, MinZ = -1050, MaxZ = 0, HexColor = "#3366ff", HQCenterX = -525, HQCenterZ = -525, HQRadius = 50 },
                    new ConfigData.NeighborhoodConfig { Name = "Eastside Disciples", Type = NeighborhoodType.East, MinX = 0, MaxX = 1050, MinZ = -1050, MaxZ = 0, HexColor = "#444444", HQCenterX = 525, HQCenterZ = -525, HQRadius = 50 }
                }
            };
        }

        protected override void SaveConfig() => Config.WriteObject(_config);

        #endregion

        #region Data Storage

        private class StoredData
        {
            public Dictionary<ulong, PlayerGangInfo> Players = new Dictionary<ulong, PlayerGangInfo>();
        }

        private class PlayerGangInfo
        {
            public NeighborhoodType HomeHood = NeighborhoodType.Neutral;
            public bool IsInfiltrator = false;
            public string CustomSet = "";
            public int Reputation = 0;
            public DateTime JoinDate = DateTime.Now;
        }

        public enum NeighborhoodType { West, North, South, East, Neutral }

        private void SaveData() => _data.WriteObject(_storedData);

        #endregion

        #region Localization

        protected override void LoadDefaultMessages()
        {
            lang.RegisterMessages(new Dictionary<string, string>
            {
                ["Welcome_Neutral"] = "Welcome to the Streets. Place your first Tool Cupboard to claim your loyalty.",
                ["Welcome_Loyal"] = "Welcome home, soldier of the <color={0}>{1}</color>.",
                ["BloodIn"] = "<color=#55ff55>BLOOD IN:</color> You are now officially repping {0} for life.",
                ["SnitchAlert"] = "<color=#ff4444>SNITCH ALERT:</color> You authorized in rival territory. You are now a marked RAT.",
                ["StreetJustice"] = "<color=#55ff55>STREET JUSTICE:</color> You cleared a Rat house. Earned {0} Scrap.",
                ["GangWarfare"] = "<color=#ff4444>[GANG WARFARE]</color> {0} took out a rival in {1}!",
                ["WhoAmI_Header"] = "--- {0} ---",
                ["WhoAmI_Loyalty"] = "Loyalty: {0}",
                ["WhoAmI_Zone"] = "Current Zone: {0}",
                ["WhoAmI_Rep"] = "Reputation: {0}",
                ["WhoAmI_Status"] = "Status: {0}",
                ["InfiltratorNews"] = "<color=#ff4444>[STREET NEWS]</color> A rival presence was detected in {0}! Check your maps for the search area.",
                ["HQ_Trespass"] = "<color=#ffaa00>WARNING:</color> You are trespassing in <color={0}>{1}</color> territory.",
                ["HQ_NoBuild_Rival"] = "<color=#ff4444>ACCESS DENIED:</color> You cannot build in enemy HQ territory.",
                ["HQ_NoBuild_TC"] = "<color=#ff4444>ACCESS DENIED:</color> Only the HQ Tool Cupboard is allowed here. Gang members can place personal items in hotel rooms.",
                ["HQ_NoBuild_Item"] = "<color=#ff4444>ACCESS DENIED:</color> Only small personal items (boxes, bags, beds) are allowed in hotel rooms.",
                ["HQ_NoAuth_Rival"] = "<color=#ff4444>ACCESS DENIED:</color> You cannot authorize on this HQ's Tool Cupboard.",
                ["HQ_TC_NoTake"] = "<color=#ff4444>ACCESS DENIED:</color> You can only deposit resources into the HQ Tool Cupboard, not withdraw.",
                ["HQ_Safezone"] = "<color=#55ff55>SAFEZONE:</color> You are now in {0} HQ. No damage can be dealt here.",
                ["HQ_TC_Registered"] = "<color=#55ff55>HQ REGISTERED:</color> This Tool Cupboard is now the official {0} headquarters TC.",
                ["HQ_NotYourHQ"] = "<color=#ff4444>ACCESS DENIED:</color> This is not your gang's HQ. You cannot build here."
            }, this);
        }

        private string GetMsg(string key, string userId = null, params object[] args) => string.Format(lang.GetMessage(key, this, userId), args);

        #endregion

        #region Oxide Hooks

        private void Init()
        {
            _instance = this;
            _data = Interface.Oxide.DataFileSystem.GetFile("HoodWars_CoreData");
            _storedData = _data.ReadObject<StoredData>() ?? new StoredData();

            permission.RegisterPermission(PermAdmin, this);
            permission.RegisterPermission(PermUse, this);
        }

        private void OnServerInitialized()
        {
            _identityTimer = timer.Every(10f, () =>
            {
                CheckProximity();
                UpdateAllIdentities();
                RefreshInfiltratorMarkers();
            });

            // Create HQ sphere markers
            if (_config != null && _config.HQ != null && _config.HQ.ShowHQSpheres)
            {
                CreateAllHQSpheres();
            }
        }

        private void Unload()
        {
            _identityTimer?.Destroy();
            ClearAllMarkers();
            ClearAllHQSpheres();
            SaveData();
        }

        private void OnPlayerConnected(BasePlayer player)
        {
            if (player == null) return;
            UpdateIdentity(player);
            
            var info = GetPlayerData(player.userID);
            if (info.HomeHood == NeighborhoodType.Neutral)
                SendReply(player, GetMsg("Welcome_Neutral", player.UserIDString));
            else
            {
                var hood = GetNeighborhoodConfig(info.HomeHood);
                SendReply(player, GetMsg("Welcome_Loyal", player.UserIDString, hood.HexColor, hood.Name));
            }

            // Sync all active markers to the joining player
            foreach (var marker in _activeMarkers.Values)
            {
                if (marker != null) marker.SendUpdate();
            }
        }

        private void OnEntityBuilt(Planner plan, GameObject go)
        {
            var cupboard = go.GetComponent<BuildingPrivlidge>();
            if (cupboard == null) return;

            BasePlayer player = plan.GetOwnerPlayer();
            if (player == null) return;

            CheckTCPlacement(cupboard, player);
        }

        private void OnCupboardAuthorize(BuildingPrivlidge privilege, BasePlayer player)
        {
            if (player == null || privilege == null) return;

            // Check HQ authorization restrictions first
            if (_config != null && _config.HQ != null && _config.HQ.EnableHQSafezones)
            {
                var hqHood = GetHQAtPosition(privilege.transform.position);
                if (hqHood != null)
                {
                    var playerInfo = GetPlayerData(player.userID);

                    // Block rivals from authorizing on HQ TCs
                    if (playerInfo.HomeHood != hqHood.Type && playerInfo.HomeHood != NeighborhoodType.Neutral)
                    {
                        SendReply(player, GetMsg("HQ_NoAuth_Rival", player.UserIDString));
                        // Schedule deauthorization on next frame since we can't modify during this hook cleanly
                        ulong targetUserId = player.userID;
                        timer.Once(0.1f, () => 
                        {
                            if (privilege != null && !privilege.IsDestroyed)
                            {
                                // Remove the player from the authorized HashSet
                                // In latest Rust, authorizedPlayers is HashSet<ulong>
                                privilege.authorizedPlayers.Remove(targetUserId);
                                privilege.SendNetworkUpdate();
                            }
                        });
                        return;
                    }
                }
            }

            CheckTCPlacement(privilege, player);
        }

        private void OnEntityKill(BaseNetworkable entity)
        {
            if (entity is BuildingPrivlidge tc)
            {
                // Check if this is an HQ TC - HQ TCs should never be killed
                // (This is a backup - damage should be blocked earlier)
                var hqHood = GetHQAtPosition(tc.transform.position);
                if (hqHood != null && _hqToolCupboards.TryGetValue(hqHood.Type, out var tcId) && tc.net?.ID == tcId)
                {
                    // Remove from HQ registry since it's being destroyed (shouldn't happen normally)
                    _hqToolCupboards.Remove(hqHood.Type);
                }

                RemoveMarker(tc.net.ID);

                // Bounty logic
                if (tc.lastAttacker is BasePlayer killer)
                {
                    var killerData = GetPlayerData(killer.userID);
                    var victimData = GetPlayerData(tc.OwnerID);

                    // Only reward if it was enemy territory
                    if (killerData.HomeHood != GetNeighborhoodAt(tc.transform.position).Type)
                    {
                        killer.GiveItem(ItemManager.CreateByItemID(-932201673, _config.General.BountyAmount));
                        SendReply(killer, GetMsg("StreetJustice", killer.UserIDString, _config.General.BountyAmount));
                        killerData.Reputation += 15;
                    }
                }
            }
        }

        private void OnPlayerDeath(BasePlayer victim, HitInfo info)
        {
            if (!_config.General.KillReveal || info == null) return;
            var killer = info.InitiatorPlayer;
            if (killer != null && killer != victim && !killer.IsNpc)
            {
                RevealPlayer(killer);
                var hood = GetNeighborhoodAt(killer.transform.position);
                PrintToChat(GetMsg("GangWarfare", null, killer.IPlayer.Name, hood.Name));
                
                // Rep adjustment
                var kData = GetPlayerData(killer.userID);
                var vData = GetPlayerData(victim.userID);
                if (kData.HomeHood != vData.HomeHood && vData.HomeHood != NeighborhoodType.Neutral)
                    kData.Reputation += 5;
            }
        }

        private object OnPlayerChat(BasePlayer player, string message, ConVar.Chat.ChatChannel channel)
        {
            if (channel != ConVar.Chat.ChatChannel.Global) return null;

            var info = GetPlayerData(player.userID);
            bool revealed = IsSpotted(player) || info.IsInfiltrator;
            
            var hoodConfig = GetNeighborhoodConfig(info.HomeHood);
            string hoodTag = hoodConfig?.Name ?? "Drifter";
            string subTag = !string.IsNullOrEmpty(info.CustomSet) ? $"[{info.CustomSet}] " : "";
            
            string nameColor = revealed ? _config.Chat.RevealedColor : _config.Chat.AnonymousColor;
            string prefix = info.IsInfiltrator ? "[RAT] " : (revealed ? "[REVEALED] " : "");
            string displayName = revealed ? player.IPlayer.Name : "ANONYMOUS";

            string formatted = $"<color={nameColor}>{prefix}{subTag}{displayName}</color> <color={_config.Chat.NeighborhoodColor}>({hoodTag})</color>: {message}";
            ConsoleNetwork.BroadcastToAllClients("chat.add", 0, player.userID, formatted);
            
            return true;
        }

        // Networking hook to ensure markers are visible [cite: TcMapMarkers]
        private object CanNetworkTo(MapMarkerGenericRadius marker, BasePlayer player)
        {
            if (marker == null || player == null) return null;
            if (_activeMarkers.Values.Contains(marker)) return true;
            return null;
        }

        #endregion

        #region HQ Safezone Hooks

        // Block all damage in HQ safezones
        private object OnEntityTakeDamage(BaseCombatEntity entity, HitInfo info)
        {
            if (_config == null || _config.HQ == null || !_config.HQ.EnableHQSafezones || entity == null || info == null) return null;

            // Let ManualDoor handle its own doors (avoid hook conflict)
            if (ManualDoor != null && entity.net != null)
            {
                try
                {
                    var result = ManualDoor.Call("API_IsManagedDoor", entity.net.ID.Value);
                    bool isDoor = result != null && (bool)result;
                    if (isDoor)
                    {
                        // Let ManualDoor handle decay damage on its doors
                        if (info.damageTypes.Has(Rust.DamageType.Decay))
                        {
                            return null;  // Let ManualDoor handle this
                        }
                        // For non-decay damage on ManualDoor doors in HQ, still block it
                    }
                }
                catch
                {
                    // API not available or returned unexpected type - continue with HoodWars logic
                }
            }

            // Check if entity is in an HQ safezone
            var hqHood = GetHQAtPosition(entity.transform.position);
            if (hqHood == null) return null;

            // Check if it's an HQ TC - these are indestructible
            if (entity is BuildingPrivlidge tc)
            {
                if (_hqToolCupboards.TryGetValue(hqHood.Type, out var tcId) && tc.net?.ID == tcId)
                {
                    // HQ TC is indestructible - nullify damage
                    info.damageTypes.ScaleAll(0f);
                    return true;
                }
            }

            // Block all damage in HQ safezone by scaling to 0
            info.damageTypes.ScaleAll(0f);
            return true;
        }

        // Block building for rivals in HQ zones and restrict TC placement
        private object CanBuild(Planner planner, Construction prefab, Construction.Target target)
        {
            if (_config == null || _config.HQ == null || !_config.HQ.EnableHQSafezones || planner == null) return null;

            var player = planner.GetOwnerPlayer();
            if (player == null) return null;

            var hqHood = GetHQAtPosition(target.position);
            if (hqHood == null) return null;

            var playerInfo = GetPlayerData(player.userID);

            // Check if player belongs to this HQ's gang
            // Neutral players are also blocked - they must join a gang first
            if (playerInfo.HomeHood != hqHood.Type)
            {
                // Rivals and neutral players cannot build anything in gang HQ
                SendReply(player, GetMsg("HQ_NoBuild_Rival", player.UserIDString));
                return false;
            }

            // Player is from this gang - check what they're trying to build
            string shortName = prefab.fullName;

            // Check if trying to place a Tool Cupboard
            bool isCupboard = shortName.EndsWith("cupboard.tool.deployed") || shortName.Contains("/cupboard.tool");
            
            if (isCupboard)
            {
                // Only allow TC placement if this HQ doesn't have one yet
                if (_hqToolCupboards.ContainsKey(hqHood.Type))
                {
                    SendReply(player, GetMsg("HQ_NoBuild_TC", player.UserIDString));
                    return false;
                }
                // Allow first TC placement
                return null;
            }

            // Allow only hotel items for gang members (not TCs at this point)
            bool isAllowedItem = _config.HQ.AllowedHotelItems.Any(allowed => 
                shortName.EndsWith(allowed) || shortName.Contains("/" + allowed));

            if (!isAllowedItem)
            {
                SendReply(player, GetMsg("HQ_NoBuild_Item", player.UserIDString));
                return false;
            }

            return null;
        }

        // Handle TC placement in HQ - register as HQ TC
        private void OnEntitySpawned(BaseNetworkable entity)
        {
            if (_config == null || _config.HQ == null || !_config.HQ.EnableHQSafezones) return;

            if (entity is BuildingPrivlidge tc)
            {
                var hqHood = GetHQAtPosition(tc.transform.position);
                if (hqHood == null) return;

                // Register this TC as the HQ TC if not already registered
                if (!_hqToolCupboards.ContainsKey(hqHood.Type))
                {
                    _hqToolCupboards[hqHood.Type] = tc.net.ID;
                    
                    // Notify the owner
                    var owner = BasePlayer.FindByID(tc.OwnerID);
                    if (owner != null)
                    {
                        SendReply(owner, GetMsg("HQ_TC_Registered", owner.UserIDString, hqHood.Name));
                    }
                }
            }
        }

        // Block taking items from HQ TC (only allow deposits)
        private object CanMoveItem(Item item, PlayerInventory playerInventory, ItemContainerId targetContainerId, int targetSlot, int amount)
        {
            if (_config == null || _config.HQ == null || !_config.HQ.EnableHQSafezones || item == null || playerInventory == null) return null;

            // Check if item is coming FROM a TC container
            var sourceContainer = item.parent;
            if (sourceContainer?.entityOwner is BuildingPrivlidge tc)
            {
                var hqHood = GetHQAtPosition(tc.transform.position);
                if (hqHood == null) return null;

                // Check if this is an HQ TC
                if (_hqToolCupboards.TryGetValue(hqHood.Type, out var tcId) && tc.net?.ID == tcId)
                {
                    // Block taking items from HQ TC
                    var player = playerInventory.baseEntity;
                    if (player != null)
                    {
                        SendReply(player, GetMsg("HQ_TC_NoTake", player.UserIDString));
                    }
                    return false;
                }
            }

            return null;
        }

        // Track player movement for trespass warnings
        private void OnPlayerTick(BasePlayer player)
        {
            if (_config == null || _config.HQ == null || !_config.HQ.EnableHQSafezones || player == null || player.IsNpc) return;

            CheckTrespassWarning(player);
        }

        #endregion

        #region Core Mechanics

        private void CheckTCPlacement(BuildingPrivlidge tc, BasePlayer player)
        {
            var info = GetPlayerData(player.userID);
            var zone = GetNeighborhoodAt(tc.transform.position);

            if (info.HomeHood == NeighborhoodType.Neutral)
            {
                if (zone.Type != NeighborhoodType.Neutral)
                {
                    info.HomeHood = zone.Type;
                    SendReply(player, GetMsg("BloodIn", player.UserIDString, zone.Name));
                    
                    // Give gang kit when player bloods in
                    Puts($"[DEBUG] Player {player.displayName} blooded into {zone.Name}, triggering GangKit...");
                    timer.Once(0.5f, () => {
                        if (GangKits != null)
                        {
                            Puts($"[DEBUG] Calling GangKits.API_GiveGangKit for {player.displayName} (gang: {zone.Name})");
                            GangKits.Call("API_GiveGangKit", player, zone.Name);
                            SendReply(player, "<color=#55ff55>GANG KIT:</color> Welcome to the gang! You've received your gang outfit and weapon.");
                        }
                        else
                        {
                            Puts("[DEBUG] GangKits plugin not found!");
                        }
                    });
                }
            }
            else if (info.HomeHood != zone.Type && zone.Type != NeighborhoodType.Neutral)
            {
                if (!info.IsInfiltrator)
                {
                    info.IsInfiltrator = true;
                    SendReply(player, GetMsg("SnitchAlert", player.UserIDString));
                }
                // Trigger reveal immediately when authorizing on enemy TC
                RevealPlayer(player);
            }

            SaveData();
            UpdateIdentity(player);
        }

        private void UpdateIdentity(BasePlayer player)
        {
            if (player == null || player.net?.connection == null) return;

            var info = GetPlayerData(player.userID);
            bool revealed = IsSpotted(player) || info.IsInfiltrator;
            
            string hoodName = GetNeighborhoodConfig(info.HomeHood)?.Name ?? "Drifter";
            string subTag = !string.IsNullOrEmpty(info.CustomSet) ? $"{info.CustomSet} | " : "";
            
            string finalName;
            if (revealed)
                finalName = info.IsInfiltrator ? $"{player.IPlayer.Name} (RAT)" : $"{player.IPlayer.Name} ({hoodName})";
            else
                finalName = $"{subTag}{hoodName}";

            if (player.displayName != finalName)
            {
                player.displayName = finalName;
                player.net.connection.username = finalName;
                player.SendNetworkUpdate();
            }
        }

        private void CheckProximity()
        {
            var players = BasePlayer.activePlayerList;
            int count = players.Count;
            if (count < 2) return;

            for (int i = 0; i < count; i++)
            {
                var p = players[i];
                if (p == null) continue;

                for (int j = i + 1; j < count; j++)
                {
                    var t = players[j];
                    if (t == null) continue;

                    if (Vector3.Distance(p.transform.position, t.transform.position) < _config.General.ProximityDistance)
                    {
                        RevealPlayer(p);
                        RevealPlayer(t);
                    }
                }
            }
        }

        private void RevealPlayer(BasePlayer player)
        {
            if (player == null) return;
            _spottedPlayers[player.userID] = Time.realtimeSinceStartup + _config.General.RevealDuration;
            UpdateIdentity(player);
            
            // Immediately refresh markers for the revealed player if they are an infiltrator
            var info = GetPlayerData(player.userID);
            if (info.IsInfiltrator) RefreshInfiltratorMarkers();
        }

        private bool IsSpotted(BasePlayer player)
        {
            if (player == null) return false;
            if (_spottedPlayers.TryGetValue(player.userID, out float expiry))
            {
                if (Time.realtimeSinceStartup < expiry) return true;
                _spottedPlayers.Remove(player.userID);
            }
            return false;
        }

        private void CreateMapMarker(BuildingPrivlidge tc)
        {
            if (tc == null || _activeMarkers.ContainsKey(tc.net.ID)) return;

            // Use the TC's network ID as a seed for a consistent but "wrong" position [cite: Search Area Logic]
            UnityEngine.Random.InitState((int)tc.net.ID.Value);
            Vector2 randomCircle = UnityEngine.Random.insideUnitCircle * _config.Markers.RandomOffsetRadius;
            Vector3 jitteredPos = tc.transform.position + new Vector3(randomCircle.x, 0, randomCircle.y);

            MapMarkerGenericRadius marker = GameManager.server.CreateEntity(PrefabMarker, jitteredPos) as MapMarkerGenericRadius;
            if (marker == null) return;

            marker.alpha = _config.Markers.InfiltratorAlpha;
            
            Color c1, c2;
            if (!ColorUtility.TryParseHtmlString(_config.Markers.Color1, out c1)) c1 = Color.red;
            if (!ColorUtility.TryParseHtmlString(_config.Markers.Color2, out c2)) c2 = Color.black;
            
            marker.color1 = c1;
            marker.color2 = c2;
            marker.radius = _config.Markers.InfiltratorRadius;
            marker.name = "RAT_ZONE";
            marker.OwnerID = tc.OwnerID;

            marker.Spawn();
            
            // Set broadcast flag and force update [cite: TcMapMarkers]
            marker.SetFlag(BaseEntity.Flags.Reserved4, true);
            marker.SendUpdate();
            marker.SendNetworkUpdate();

            _activeMarkers[tc.net.ID] = marker;
            
            var hood = GetNeighborhoodAt(tc.transform.position);
            PrintToChat(GetMsg("InfiltratorNews", null, hood.Name));
        }

        private void RemoveMarker(NetworkableId id)
        {
            if (_activeMarkers.TryGetValue(id, out var marker))
            {
                if (marker != null && !marker.IsDestroyed)
                {
                    marker.Kill();
                    marker.SendUpdate();
                }
                _activeMarkers.Remove(id);
            }
        }

        private void RefreshInfiltratorMarkers()
        {
            // Gather all current TCs on the server
            var allTCs = BaseNetworkable.serverEntities.OfType<BuildingPrivlidge>().ToList();
            
            // Check each TC: Should it have a marker right now?
            foreach (var tc in allTCs)
            {
                if (tc == null || tc.OwnerID == 0) continue;
                
                var owner = BasePlayer.FindByID(tc.OwnerID);
                var info = GetPlayerData(tc.OwnerID);
                var zone = GetNeighborhoodAt(tc.transform.position);

                // Conditions for a marker:
                // 1. Owner is an Infiltrator
                // 2. TC is in rival territory
                // 3. Owner is CURRENTLY REVEALED (Spotted)
                bool shouldShow = info.IsInfiltrator && 
                                 info.HomeHood != zone.Type && 
                                 zone.Type != NeighborhoodType.Neutral &&
                                 (owner != null && IsSpotted(owner));

                if (shouldShow)
                {
                    if (!_activeMarkers.ContainsKey(tc.net.ID)) CreateMapMarker(tc);
                }
                else
                {
                    if (_activeMarkers.ContainsKey(tc.net.ID)) RemoveMarker(tc.net.ID);
                }
            }
        }

        private void ClearAllMarkers()
        {
            foreach (var marker in _activeMarkers.Values)
            {
                if (marker != null && !marker.IsDestroyed)
                {
                    marker.Kill();
                    marker.SendUpdate();
                }
            }
            _activeMarkers.Clear();
        }

        private void UpdateAllIdentities()
        {
            foreach (var p in BasePlayer.activePlayerList) UpdateIdentity(p);
        }

        #region HQ Sphere Visual Markers

        private void CreateAllHQSpheres()
        {
            foreach (var hood in _config.Neighborhoods)
            {
                if (hood.Type == NeighborhoodType.Neutral) continue;
                CreateHQSphere(hood);
            }
        }

        private void CreateHQSphere(ConfigData.NeighborhoodConfig hood)
        {
            if (hood == null || hood.Type == NeighborhoodType.Neutral) return;
            
            // Remove existing sphere if any
            RemoveHQSphere(hood.Type);

            // Create sphere at HQ center (positioned slightly above terrain)
            float terrainHeight = TerrainMeta.HeightMap.GetHeight(new Vector3(hood.HQCenterX, 0, hood.HQCenterZ));
            Vector3 center = new Vector3(hood.HQCenterX, terrainHeight + 1f, hood.HQCenterZ);
            
            var sphere = GameManager.server.CreateEntity(PrefabSphere, center) as SphereEntity;
            if (sphere == null) return;

            // SphereEntity.currentRadius is actually the diameter, so radius * 2 gives correct visual size
            sphere.currentRadius = hood.HQRadius * 2f;
            sphere.lerpSpeed = 0f;
            
            sphere.Spawn();

            // Set sphere color based on gang color
            Color gangColor;
            if (!ColorUtility.TryParseHtmlString(hood.HexColor, out gangColor))
            {
                gangColor = Color.white;
            }

            // Apply color and transparency to the sphere
            var renderer = sphere.GetComponentInChildren<MeshRenderer>();
            if (renderer != null && renderer.sharedMaterial != null)
            {
                // Create a new material instance to avoid affecting other spheres
                var mat = new Material(renderer.sharedMaterial);
                gangColor.a = _config.HQ.HQSphereAlpha;
                mat.color = gangColor;
                renderer.material = mat;
            }

            _hqSphereMarkers[hood.Type] = sphere;
        }

        private void RemoveHQSphere(NeighborhoodType type)
        {
            if (_hqSphereMarkers.TryGetValue(type, out var sphere))
            {
                if (sphere != null && !sphere.IsDestroyed)
                {
                    sphere.Kill();
                }
                _hqSphereMarkers.Remove(type);
            }
        }

        private void ClearAllHQSpheres()
        {
            foreach (var sphere in _hqSphereMarkers.Values)
            {
                if (sphere != null && !sphere.IsDestroyed)
                {
                    sphere.Kill();
                }
            }
            _hqSphereMarkers.Clear();
        }

        private void RefreshAllHQSpheres()
        {
            ClearAllHQSpheres();
            if (_config.HQ.ShowHQSpheres)
            {
                CreateAllHQSpheres();
            }
        }

        #endregion

        #endregion

        #region Helpers & Commands

        private PlayerGangInfo GetPlayerData(ulong id)
        {
            if (!_storedData.Players.TryGetValue(id, out var info))
            {
                info = new PlayerGangInfo();
                _storedData.Players[id] = info;
            }
            return info;
        }

        private ConfigData.NeighborhoodConfig GetNeighborhoodAt(Vector3 pos)
        {
            foreach (var hood in _config.Neighborhoods)
            {
                if (pos.x >= hood.MinX && pos.x <= hood.MaxX && pos.z >= hood.MinZ && pos.z <= hood.MaxZ)
                    return hood;
            }
            return new ConfigData.NeighborhoodConfig { Name = "Neutral Ground", Type = NeighborhoodType.Neutral, HexColor = "#aaaaaa" };
        }

        private ConfigData.NeighborhoodConfig GetNeighborhoodConfig(NeighborhoodType type)
        {
            return _config.Neighborhoods.FirstOrDefault(x => x.Type == type);
        }

        // Get the HQ neighborhood config if position is within an HQ radius
        private ConfigData.NeighborhoodConfig GetHQAtPosition(Vector3 pos)
        {
            if (_config == null || _config.Neighborhoods == null) return null;

            foreach (var hood in _config.Neighborhoods)
            {
                if (hood.Type == NeighborhoodType.Neutral) continue;

                float distance = Vector3.Distance(
                    new Vector3(hood.HQCenterX, pos.y, hood.HQCenterZ),
                    pos
                );

                if (distance <= hood.HQRadius)
                {
                    return hood;
                }
            }
            return null;
        }

        // Check and send trespass warnings to players in enemy HQ
        private void CheckTrespassWarning(BasePlayer player)
        {
            if (player == null) return;

            var hqHood = GetHQAtPosition(player.transform.position);
            if (hqHood == null) return;

            var playerInfo = GetPlayerData(player.userID);

            // If player is neutral, they haven't chosen a gang yet
            if (playerInfo.HomeHood == NeighborhoodType.Neutral) return;

            // If player is in their own HQ, show safezone message (with cooldown)
            if (playerInfo.HomeHood == hqHood.Type)
            {
                if (CanShowTrespassWarning(player.userID, hqHood.Type))
                {
                    SendReply(player, GetMsg("HQ_Safezone", player.UserIDString, hqHood.Name));
                    SetTrespassWarningCooldown(player.userID, hqHood.Type);
                }
                return;
            }

            // Player is in enemy HQ - show trespass warning
            if (CanShowTrespassWarning(player.userID, hqHood.Type))
            {
                SendReply(player, GetMsg("HQ_Trespass", player.UserIDString, hqHood.HexColor, hqHood.Name));
                SetTrespassWarningCooldown(player.userID, hqHood.Type);
            }
        }

        private bool CanShowTrespassWarning(ulong playerId, NeighborhoodType hqType)
        {
            if (!_trespassWarningCooldowns.TryGetValue(playerId, out var cooldowns))
                return true;

            if (!cooldowns.TryGetValue(hqType, out float expiry))
                return true;

            return Time.realtimeSinceStartup >= expiry;
        }

        private void SetTrespassWarningCooldown(ulong playerId, NeighborhoodType hqType)
        {
            if (!_trespassWarningCooldowns.TryGetValue(playerId, out var cooldowns))
            {
                cooldowns = new Dictionary<NeighborhoodType, float>();
                _trespassWarningCooldowns[playerId] = cooldowns;
            }

            cooldowns[hqType] = Time.realtimeSinceStartup + _config.HQ.TrespassWarningInterval;
        }

        // Check if a TC is an HQ TC
        private bool IsHQToolCupboard(BuildingPrivlidge tc)
        {
            if (tc == null) return false;

            var hqHood = GetHQAtPosition(tc.transform.position);
            if (hqHood == null) return false;

            return _hqToolCupboards.TryGetValue(hqHood.Type, out var tcId) && tc.net?.ID == tcId;
        }

        [ChatCommand("gangname")]
        private void CmdGangName(BasePlayer player, string command, string[] args)
        {
            var info = GetPlayerData(player.userID);
            if (args.Length == 0)
            {
                info.CustomSet = "";
                SendReply(player, "Your sub-gang name has been cleared.");
            }
            else
            {
                string name = string.Join(" ", args).ToUpper();
                if (name.Length > 15) { SendReply(player, "Name too long (max 15 characters)."); return; }
                info.CustomSet = name;
                SendReply(player, $"You are now repping the <color=#ffaa00>{name}</color> set.");
            }
            UpdateIdentity(player);
            SaveData();
        }

        [ChatCommand("whoami")]
        private void CmdWhoAmI(BasePlayer player, string command, string[] args)
        {
            var info = GetPlayerData(player.userID);
            var hood = GetNeighborhoodAt(player.transform.position);
            
            string status = IsSpotted(player) ? "<color=#ff4444>REVEALED</color>" : "<color=#55ff55>HIDDEN</color>";
            if (info.IsInfiltrator) status = "<color=#ff0000>WANTED RAT</color>";

            string hoodName = GetNeighborhoodConfig(info.HomeHood)?.Name ?? "Drifter";

            string msg = GetMsg("WhoAmI_Header", player.UserIDString, player.displayName) + "\n" +
                         GetMsg("WhoAmI_Loyalty", player.UserIDString, hoodName) + "\n" +
                         GetMsg("WhoAmI_Zone", player.UserIDString, hood.Name) + "\n" +
                         GetMsg("WhoAmI_Rep", player.UserIDString, info.Reputation) + "\n" +
                         GetMsg("WhoAmI_Status", player.UserIDString, status);
            
            SendReply(player, msg);
        }

        [ConsoleCommand("hood.clear")]
        private void ConsoleClear(ConsoleSystem.Arg arg)
        {
            if (!arg.IsAdmin) return;
            ClearAllMarkers();
            Puts("All gang markers cleared from map.");
        }

        [ConsoleCommand("hood.refresh")]
        private void ConsoleRefresh(ConsoleSystem.Arg arg)
        {
            if (!arg.IsAdmin) return;
            RefreshInfiltratorMarkers();
            Puts("Map markers refreshed based on current TCs.");
        }

        [ConsoleCommand("hood.resetplayer")]
        private void ConsoleResetPlayer(ConsoleSystem.Arg arg)
        {
            if (!arg.IsAdmin || !arg.HasArgs()) return;
            ulong id;
            if (!ulong.TryParse(arg.Args[0], out id))
            {
                Puts("Invalid SteamID provided.");
                return;
            }
            
            if (_storedData.Players.Remove(id))
            {
                Puts($"Reset gang data for {id}");
                SaveData();
            }
        }

        #endregion

        #region Admin GUI

        private const string AdminUIName = "HoodWars_AdminUI";
        private const string AdminUIOverlay = "HoodWars_AdminOverlay";
        private Dictionary<ulong, int> _adminUIPage = new Dictionary<ulong, int>();
        private Dictionary<ulong, string> _adminUISection = new Dictionary<ulong, string>();

        [ConsoleCommand("hoodwars.admin")]
        private void ConsoleHoodAdmin(ConsoleSystem.Arg arg)
        {
            var player = arg.Player();
            if (player == null) return;
            if (!permission.UserHasPermission(player.UserIDString, PermAdmin) && !player.IsAdmin) return;

            if (!arg.HasArgs()) return;

            // Ensure config is loaded before processing any commands
            if (_config == null)
            {
                SendReply(player, "<color=#ff4444>ERROR:</color> Configuration not loaded. Please reload the plugin.");
                return;
            }

            string action = arg.Args[0].ToLower();

            switch (action)
            {
                case "close"
// ... [Code preview truncated due to length. Full source in attached file] ...

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