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

FREE DeathmatchSoccer : 5.4.0 5.4.0

Нет прав для скачивания
C#:
using Oxide.Core;
using Oxide.Core.Plugins;
using Oxide.Core.Libraries;
using Oxide.Core.Libraries.Covalence;
using Oxide.Game.Rust.Cui;
using UnityEngine;
using System.Collections.Generic;
using Newtonsoft.Json;
using System;
namespace Oxide.Plugins {
    /*
     * DeathmatchSoccer - 3-Team Soccer Battle Plugin with Goal Swapping Rotation
     * 
     * FEATURES:
     * - 3-Team System: Blue (SHELL-SEA/GRUB), Red (Loot-pool/DOORCAMPER), Black (PZG/ROAMER)
     * - Goal Swapping Rotation: 2 teams play, losing team's goal is replaced by waiting team's goal
     * - Custom Skins: Each team can have unique skins via Skins.cs plugin
     * - Modern UI: Team selection menu, dynamic scoreboard, role selection
     * - 2 Roles: Striker (100HP, Thompson) and Goalie (200HP, SPAS-12)
     * - Active Goal System: Only active goals count for scoring
     * 
     * ROTATION SYSTEM:
     * - 2 black goals placed (one at red position, one at blue position)
     * - When a team loses, their goal is deactivated and black goal at that position activates
     * - Winner continues at their goal, black team takes over loser's goal position
     * - No teleportation needed - seamless goal swapping
     * 
     * ADMIN COMMANDS:
     * /set_red, /set_blue - Set red and blue goal positions
     * /set_black1, /set_black2 - Set black goals (at red and blue positions)
     * /set_center - Set ball spawn position
     * /set_lobby_spawn - Set lobby spawn point where players teleport during lobby
     * /save_goals, /load_goals - Persist arena data
     * /start_match - Begin the match
     * /rotation - Toggle rotation mode ON/OFF
     * /setskin <team> <item> <skinId> - Configure team skins
     * /showskins - Display all skin configurations
     * /goal_debug - Toggle goal zone visualization (shows active/inactive goals)
     * 
     * PLAYER COMMANDS:
     * /join [team] - Join a team (shows UI if no team specified)
     * /teams - Show team selection UI
     * Use /join command - Shows team selection UI (reminders appear during lobby)
     */
    [Info("DeathmatchSoccer", "KillaDome", "5.4.0")]
    [Description("3-Team Soccer with Lobby System and Celebrations")]
    public class DeathmatchSoccer : RustPlugin {
        // ==========================================
        // 1. CONFIGURATION
        // ==========================================
        private string middlewareUrl = "http://165.22.174.250/chat";
        private string licenseKey = "2c573abe-3172-4fc9-b834-ba4c70fb1eb8";
        // SETTINGS
        private float KickForceMultiplier = 3500.0f;
        private float MaxKickDistance = 15.0f;
        private float LeashRadius = 15.0f;
        private int ScoreToWin = 5;
        // GOAL BOX (Overwritten by LoadData)
        private float GoalWidth = 8.0f;
        private float GoalHeight = 4.0f;
        private float GoalDepth = 6.0f;
        // IMAGES
        // 3 Scoreboard backgrounds for different matchups
        private string ImgScoreboardBgRedBlue = "https://i.imgur.com/rInAKIa.png";
        private string ImgScoreboardBgBlackRed = "https://i.imgur.com/T6MqVYH.png";
        // TODO: Replace with actual URL
        private string ImgScoreboardBgBlueBlack = "https://i.imgur.com/ygeaW0I.png";
        // TODO: Replace with actual URL
        // 3 Goal banner images for different matchups
        private string ImgGoalBannerRedBlue = "https://i.imgur.com/Jb9y1Xm.png";
        private string ImgGoalBannerBlackRed = "https://i.imgur.com/8KqZx4Y.png";
        private string ImgGoalBannerBlueBlack = "https://i.imgur.com/5LmNp2X.png";
        [PluginReference]
        Plugin ImageLibrary;
        [PluginReference]
        Plugin Skins;
        // CUSTOM SKIN IDS (Configure these for each team)
        private Dictionary<string, TeamSkins> teamSkins = new Dictionary<string, TeamSkins> {
            {
                "blue", new TeamSkins {
                    TshirtSkin = 3619180626,  // Blue team tshirt
                    PantsSkin = 3619368981,  // Blue team pants
                    TorsoSkin = 3619178918,  // Blue team metal.plate.torso
                    FacemaskSkin = 3619365504,  // Blue team metal.facemask
                    ShoesSkin = 3619430819,  // Blue team burlap.shoes
                    WeaponSkin = 0,  // Blue team thompson
                    GoaliePantsSkin = 0,  // Blue goalie heavy.plate.pants
                    GoalieJacketSkin = 0,  // Blue goalie heavy.plate.jacket
                    GoalieWeaponSkin = 0  // Blue goalie spas12
                }
            }
            , {
                "red", new TeamSkins {
                    TshirtSkin = 3619182996,  // Red team tshirt
                    PantsSkin = 3619357729,  // Red team pants
                    TorsoSkin = 3619182046,  // Red team metal.plate.torso
                    FacemaskSkin = 3619366404,  // Red team metal.facemask
                    ShoesSkin = 3619432687,  // Red team burlap.shoes
                    WeaponSkin = 0,  // Red team thompson
                    GoaliePantsSkin = 0,  // Red goalie heavy.plate.pants
                    GoalieJacketSkin = 0,  // Red goalie heavy.plate.jacket
                    GoalieWeaponSkin = 0  // Red goalie spas12
                }
            }
            , {
                "black", new TeamSkins {
                    TshirtSkin = 3618727245,  // Black team tshirt
                    PantsSkin = 3619358770,  // Black team pants
                    TorsoSkin = 3619177448,  // Black team metal.plate.torso
                    FacemaskSkin = 3619364394,  // Black team metal.facemask
                    ShoesSkin = 3619429117,  // Black team burlap.shoes
                    WeaponSkin = 0,  // Black team thompson
                    GoaliePantsSkin = 0,  // Black goalie heavy.plate.pants
                    GoalieJacketSkin = 0,  // Black goalie heavy.plate.jacket
                    GoalieWeaponSkin = 0  // Black goalie spas12
                }
            }
        }
        ;
        private class TeamSkins {
            public ulong TshirtSkin {
                get;
                set;
            }
            public ulong PantsSkin {
                get;
                set;
            }
            public ulong TorsoSkin {
                get;
                set;
            }
            public ulong FacemaskSkin {
                get;
                set;
            }
            public ulong ShoesSkin {
                get;
                set;
            }
            public ulong WeaponSkin {
                get;
                set;
            }
            public ulong GoaliePantsSkin {
                get;
                set;
            }
            public ulong GoalieJacketSkin {
                get;
                set;
            }
            public ulong GoalieWeaponSkin {
                get;
                set;
            }
        }
        // STATE
        private BaseEntity activeBall;
        private BasePlayer lastKicker;
        private Vector3 redGoalPos, blueGoalPos, blackGoalPos1, blackGoalPos2, centerPos;
        private Quaternion redGoalRot, blueGoalRot, blackGoalRot1, blackGoalRot2;
        private int scoreRed = 0;
        private int scoreBlue = 0;
        private int scoreBlack = 0;
        private bool gameActive = false;
        private bool matchStarted = false;
        private bool debugActive = false;
        // ROTATION SYSTEM - Goal Swapping (2 play, 1 waits)
        private bool rotationMode = true;
        // Enable rotation by default
        private string waitingTeam = "black";
        // Team waiting for next match
        private string team1Playing = "blue";
        private string team2Playing = "red";
        private int matchNumber = 1;
        private int maxMatchesPerTournament = 2;
        // Tournament ends after 2 matches
        // LOBBY SYSTEM
        private bool lobbyActive = true;
        private Vector3 lobbySpawnPos = Vector3.zero;
        private Vector3 loserSpawnPos = Vector3.zero;
        // Spawn point for losing team after match
        private Timer lobbyTimer;
        private Timer lobbyReminderTimer;
        private int lobbyCountdown = 0;
        // CELEBRATION SYSTEM
        private List<string> celebrationMessages = new List<string>();
        private Timer celebrationTimer;
        // KILL FEED SYSTEM
        private List<KillFeedEntry> killFeed = new List<KillFeedEntry>();
        private Timer killFeedTimer;
        private const int MAX_KILL_FEED_ENTRIES = 5;
        // ACTIVE GOALS - Track which goals are currently in play
        private Dictionary<string, bool> activeGoals = new Dictionary<string, bool> {
            {
                "red", true
            }
            , {
                "blue", true
            }
            , {
                "black1", false
            }
            ,  // Black goal at red position (inactive initially)
            {
                "black2", false
            }
            // Black goal at blue position (inactive initially)
        }
        ;
        // TEAM CONFIGURATIONS
        private Dictionary<string, TeamConfig> teamConfigs = new Dictionary<string, TeamConfig> {
            {
                "blue", new TeamConfig {
                    Name = "SHELL-SEA FOOTBALL CLUB", Tag = "GRUB", Color = "0.2 0.4 1", HexColor = "#3366FF"
                }
            }
            , {
                "red", new TeamConfig {
                    Name = "Loot-pool F.C.", Tag = "DOORCAMPER", Color = "1 0.2 0.2", HexColor = "#FF3333"
                }
            }
            , {
                "black", new TeamConfig {
                    Name = "Project Zerg-Germain", Tag = "ROAMER", Color = "0.2 0.2 0.2", HexColor = "#333333"
                }
            }
        }
        ;
        private Timer gameTimer, tickerTimer, hudTimer, debugTimer;
        private Dictionary<ulong, bool> ballRangeState = new Dictionary<ulong, bool>();
        // TICKER
        private List<string> tickerMessages = new List<string> {
            "GOAL SWAPPING ROTATION", "LOSER'S GOAL REPLACED BY WAITING TEAM", "SHOOT BALL TO SCORE", "KILL ENEMIES", "FIRST TO 5 WINS"
        }
        ;
        private int tickerIndex = 0;
        // TEAMS
        private List<ulong> redTeam = new List<ulong>();
        private List<ulong> blueTeam = new List<ulong>();
        private List<ulong> blackTeam = new List<ulong>();
        private Dictionary<ulong, string> playerRoles = new Dictionary<ulong, string>();
        // TEAM CONFIG CLASS
        private class TeamConfig {
            public string Name {
                get;
                set;
            }
            public string Tag {
                get;
                set;
            }
            public string Color {
                get;
                set;
            }
            public string HexColor {
                get;
                set;
            }
        }
        // KILL FEED ENTRY CLASS
        private class KillFeedEntry {
            public string KillerName {
                get;
                set;
            }
            public string VictimName {
                get;
                set;
            }
            public string KillerTeam {
                get;
                set;
            }
            public string VictimTeam {
                get;
                set;
            }
            public string Message {
                get;
                set;
            }
            public float Timestamp {
                get;
                set;
            }
        }
        // DATA FILE
        private const string DataFileName = "DeathmatchSoccer_Data";
        // ==========================================
        // 2. DATA PERSISTENCE (SAVING/LOADING)
        // ==========================================
        private class ArenaData {
            public float Rx, Ry, Rz;
            // Red Pos
            public float Bx, By, Bz;
            // Blue Pos
            public float Bl1x, Bl1y, Bl1z;
            // Black1 Pos (at red position)
            public float Bl2x, Bl2y, Bl2z;
            // Black2 Pos (at blue position)
            public float Cx, Cy, Cz;
            // Center Pos
            public float Rqx, Rqy, Rqz, Rqw;
            // Red Rot
            public float Bqx, Bqy, Bqz, Bqw;
            // Blue Rot
            public float Bl1qx, Bl1qy, Bl1qz, Bl1qw;
            // Black1 Rot
            public float Bl2qx, Bl2qy, Bl2qz, Bl2qw;
            // Black2 Rot
            public float Gw, Gh, Gd;
            // Dimensions
            public float Lbx, Lby, Lbz;
            // Lobby spawn position
            public float Lsx, Lsy, Lsz;
            // Loser spawn position
        }
        private void SaveArenaData() {
            Puts($"[Data] Saving arena data to file");
            var data = new ArenaData {
                Rx = redGoalPos.x, Ry = redGoalPos.y, Rz = redGoalPos.z, Bx = blueGoalPos.x, By = blueGoalPos.y, Bz = blueGoalPos.z, Bl1x = blackGoalPos1.x, Bl1y = blackGoalPos1.y, Bl1z = blackGoalPos1.z, Bl2x = blackGoalPos2.x, Bl2y = blackGoalPos2.y, Bl2z = blackGoalPos2.z, Cx = centerPos.x, Cy = centerPos.y, Cz = centerPos.z, Rqx = redGoalRot.x, Rqy = redGoalRot.y, Rqz = redGoalRot.z, Rqw = redGoalRot.w, Bqx = blueGoalRot.x, Bqy = blueGoalRot.y, Bqz = blueGoalRot.z, Bqw = blueGoalRot.w, Bl1qx = blackGoalRot1.x, Bl1qy = blackGoalRot1.y, Bl1qz = blackGoalRot1.z, Bl1qw = blackGoalRot1.w, Bl2qx = blackGoalRot2.x, Bl2qy = blackGoalRot2.y, Bl2qz = blackGoalRot2.z, Bl2qw = blackGoalRot2.w, Gw = GoalWidth, Gh = GoalHeight, Gd = GoalDepth, Lbx = lobbySpawnPos.x, Lby = lobbySpawnPos.y, Lbz = lobbySpawnPos.z, Lsx = loserSpawnPos.x, Lsy = loserSpawnPos.y, Lsz = loserSpawnPos.z
            }
            ;
            Puts($"[Data] Lobby spawn saved: {lobbySpawnPos}");
            Puts($"[Data] Loser spawn saved: {loserSpawnPos}");
            Interface.Oxide.DataFileSystem.WriteObject(DataFileName, data);
            Puts($"[Data] Arena data saved successfully to {DataFileName}");
        }
        private void LoadArenaData() {
            Puts($"[Data] Loading arena data from file");
            if (Interface.Oxide.DataFileSystem.ExistsDatafile(DataFileName)) {
                try {
                    var data = Interface.Oxide.DataFileSystem.ReadObject<ArenaData>(DataFileName);
                    if (data != null) {
                        redGoalPos = new Vector3(data.Rx, data.Ry, data.Rz);
                        blueGoalPos = new Vector3(data.Bx, data.By, data.Bz);
                        blackGoalPos1 = new Vector3(data.Bl1x, data.Bl1y, data.Bl1z);
                        blackGoalPos2 = new Vector3(data.Bl2x, data.Bl2y, data.Bl2z);
                        centerPos = new Vector3(data.Cx, data.Cy, data.Cz);
                        redGoalRot = new Quaternion(data.Rqx, data.Rqy, data.Rqz, data.Rqw);
                        blueGoalRot = new Quaternion(data.Bqx, data.Bqy, data.Bqz, data.Bqw);
                        blackGoalRot1 = new Quaternion(data.Bl1qx, data.Bl1qy, data.Bl1qz, data.Bl1qw);
                        blackGoalRot2 = new Quaternion(data.Bl2qx, data.Bl2qy, data.Bl2qz, data.Bl2qw);
                        if (data.Gw > 0) {
                            GoalWidth = data.Gw;
                            GoalHeight = data.Gh;
                            GoalDepth = data.Gd;
                        }
                        // Load spawn positions
                        lobbySpawnPos = new Vector3(data.Lbx, data.Lby, data.Lbz);
                        loserSpawnPos = new Vector3(data.Lsx, data.Lsy, data.Lsz);
                        Puts($"[Data] Loaded lobby spawn: {lobbySpawnPos}");
                        Puts($"[Data] Loaded loser spawn: {loserSpawnPos}");
                        Puts("[Data] Arena data loaded successfully.");
                    }
                    else {
                        Puts("[Data] ERROR: Arena data file exists but data is null");
                    }
                }
                catch (System.Exception ex) {
                    Puts($"[Data] ERROR: Failed to load arena data - {ex.Message}");
                }
            }
            else {
                Puts($"[Data] No arena data file found at {DataFileName}, using defaults");
            }
        }
        // ==========================================
        // 3. LIFECYCLE
        // ==========================================
        void OnServerInitialized() {
            Puts("═══════════════════════════════════");
            Puts("DeathmatchSoccer Plugin Loaded!");
            Puts("Version: 5.4.0");
            Puts("═══════════════════════════════════");
            LoadArenaData();
            // Load saved goals
            if (ImageLibrary != null) {
                // Register 3 scoreboard backgrounds for different matchups
                ImageLibrary.Call("AddImage", ImgScoreboardBgRedBlue, "Soccer_Bar_BG_RedBlue");
                ImageLibrary.Call("AddImage", ImgScoreboardBgBlackRed, "Soccer_Bar_BG_BlackRed");
                ImageLibrary.Call("AddImage", ImgScoreboardBgBlueBlack, "Soccer_Bar_BG_BlueBlack");
                // Register 3 goal banner images for different matchups
                ImageLibrary.Call("AddImage", ImgGoalBannerRedBlue, "Soccer_Goal_Banner_RedBlue");
                ImageLibrary.Call("AddImage", ImgGoalBannerBlackRed, "Soccer_Goal_Banner_BlackRed");
                ImageLibrary.Call("AddImage", ImgGoalBannerBlueBlack, "Soccer_Goal_Banner_BlueBlack");
            }
            Puts("DeathmatchSoccer: Hooks registered successfully");
            Puts("DeathmatchSoccer: OnEntityBuilt hook should now be active");
        }
        void Unload() {
            if (activeBall != null && !activeBall.IsDestroyed) activeBall.Kill();
            if (gameTimer != null) gameTimer.Destroy();
            if (tickerTimer != null) tickerTimer.Destroy();
            if (hudTimer != null) hudTimer.Destroy();
            if (debugTimer != null) debugTimer.Destroy();
            // Cleanup entity timers
            foreach (var timer in entityTimers.Values) {
                timer?.Destroy();
            }
            entityTimers.Clear();
            foreach (var player in BasePlayer.activePlayerList) {
                CuiHelper.DestroyUi(player, "SoccerScoreboard");
                CuiHelper.DestroyUi(player, "SoccerTicker");
                CuiHelper.DestroyUi(player, "GoalBanner");
                CuiHelper.DestroyUi(player, "BallRangeHUD");
                CuiHelper.DestroyUi(player, "RoleSelectUI");
                CuiHelper.DestroyUi(player, "TeamSelectUI");
                CuiHelper.DestroyUi(player, "LeashHUD");
            }
        }
        // ==========================================
        // 4. ADMIN COMMANDS
        // ==========================================
        [ChatCommand("save_goals")]
        private void CmdSaveGoals(BasePlayer player, string command, string[] args) {
            if (!player.IsAdmin) return;
            SaveArenaData();
            SendReply(player, "Arena Data Saved! Positions will persist.");
        }
        [ChatCommand("load_goals")]
        private void CmdLoadGoals(BasePlayer player, string command, string[] args) {
            if (!player.IsAdmin) return;
            LoadArenaData();
            SendReply(player, "Arena Data Reloaded.");
            if (redGoalPos != Vector3.zero) DrawGoal(player, redGoalPos, redGoalRot, Color.red, 5f);
        }
        [ChatCommand("start_match")]
        private void CmdStartMatch(BasePlayer player, string command, string[] args) {
            if (!player.IsAdmin) return;
            if (centerPos == Vector3.zero) {
                SendReply(player, "Error: Set Center first!");
                return;
            }
            scoreRed = 0;
            scoreBlue = 0;
            scoreBlack = 0;
            matchNumber = 1;
            if (rotationMode) {
                // Set initial rotation: blue vs red, black waits
                team1Playing = "blue";
                team2Playing = "red";
                waitingTeam = "black";
                // Activate red and blue goals, deactivate black goals
                activeGoals["red"] = true;
                activeGoals["blue"] = true;
                activeGoals["black1"] = false;
                activeGoals["black2"] = false;
                PrintToChat($"ROTATION MATCH #{matchNumber}: {teamConfigs[team1Playing].Tag} vs {teamConfigs[team2Playing].Tag}");
                PrintToChat($"Next Team: {teamConfigs[waitingTeam].Tag}");
            }
            else {
                // In 3-way mode, activate all original goals
                activeGoals["red"] = true;
                activeGoals["blue"] = true;
                activeGoals["black1"] = false;
                activeGoals["black2"] = false;
                PrintToChat("MATCH STARTED! 3 Teams Battle!");
            }
            gameActive = true;
            matchStarted = true;
            SpawnBall();
            RefreshScoreboardAll();
            StartTicker();
            if (gameTimer != null) gameTimer.Destroy();
            gameTimer = timer.Repeat(0.05f, 0, CheckGoals);
            if (hudTimer != null) hudTimer.Destroy();
            hudTimer = timer.Repeat(0.5f, 0, HudLoop);
            PrintToChat("MATCH STARTED! 3 Teams Battle!");
            CallMiddleware("EVENT: MATCH_START. Score 0-0-0. 3-Team Battle.");
        }
        [ChatCommand("goal_size")]
        private void CmdSetSize(BasePlayer player, string command, string[] args) {
            if (!player.IsAdmin) return;
            if (args.Length < 3) {
                SendReply(player, $"Current: {GoalWidth}x{GoalHeight}x{GoalDepth}");
                return;
            }
            if (float.TryParse(args[0], out float w) && float.TryParse(args[1], out float h) && float.TryParse(args[2], out float d)) {
                GoalWidth = w;
                GoalHeight = h;
                GoalDepth = d;
                SendReply(player, $"Goal Updated: {w}x{h}x{d}. Use /save_goals to keep.");
            }
        }
        [ChatCommand("set_red")]
        private void CmdSetRed(BasePlayer p, string c, string[] a) {
            if(p.IsAdmin) {
                redGoalPos=p.transform.position;
                redGoalRot=p.transform.rotation;
                SendReply(p, "Red Goal Set.");
                DrawGoal(p, redGoalPos, redGoalRot, Color.red, 5f);
            }
        }
        [ChatCommand("set_blue")]
        private void CmdSetBlue(BasePlayer p, string c, string[] a) {
            if(p.IsAdmin) {
                blueGoalPos=p.transform.position;
                blueGoalRot=p.transform.rotation;
                SendReply(p, "Blue Goal Set.");
                DrawGoal(p, blueGoalPos, blueGoalRot, Color.blue, 5f);
            }
        }
        [ChatCommand("set_black1")]
        private void CmdSetBlack1(BasePlayer p, string c, string[] a) {
            if(p.IsAdmin) {
                blackGoalPos1=p.transform.position;
                blackGoalRot1=p.transform.rotation;
                SendReply(p, "Black Goal 1 Set (at Red position).");
                DrawGoal(p, blackGoalPos1, blackGoalRot1, Color.black, 5f);
            }
        }
        [ChatCommand("set_black2")]
        private void CmdSetBlack2(BasePlayer p, string c, string[] a) {
            if(p.IsAdmin) {
                blackGoalPos2=p.transform.position;
                blackGoalRot2=p.transform.rotation;
                SendReply(p, "Black Goal 2 Set (at Blue position).");
                DrawGoal(p, blackGoalPos2, blackGoalRot2, Color.black, 5f);
            }
        }
        [ChatCommand("set_center")]
        private void CmdSetCenter(BasePlayer p, string c, string[] a) {
            if(p.IsAdmin) {
                centerPos=p.transform.position;
                SendReply(p, "Center Set.");
            }
        }
        [ChatCommand("set_lobby_spawn")]
        private void CmdSetLobbySpawn(BasePlayer p, string c, string[] a) {
            if(!p.IsAdmin) return;
            Puts($"[LobbySpawn] Admin {p.displayName} running /set_lobby_spawn at position {p.transform.position}");
            // Try to find ground below player for better spawn position
            RaycastHit hit;
            Vector3 rayStart = p.transform.position + Vector3.up;
            Puts($"[LobbySpawn] Performing raycast from {rayStart} going down");
            // Try with default layers first
            if (Physics.Raycast(rayStart, Vector3.down, out hit, 10f, LayerMask.GetMask("Terrain", "World", "Construction"))) {
                lobbySpawnPos = hit.point + new Vector3(0, 0.5f, 0);
                // Slightly above ground
                Puts($"[LobbySpawn] Ground found at {hit.point}, setting spawn 0.5m above at: {lobbySpawnPos}");
            }
            // Try with all layers as fallback
            else if (Physics.Raycast(rayStart, Vector3.down, out hit, 10f)) {
                lobbySpawnPos = hit.point + new Vector3(0, 0.5f, 0);
                Puts($"[LobbySpawn] Ground found (all layers) at {hit.point}, setting spawn at: {lobbySpawnPos}");
            }
            // Use player position as final fallback
            else {
                lobbySpawnPos = p.transform.position;
                Puts($"[LobbySpawn] No ground found via raycast, using player position: {lobbySpawnPos}");
            }
            SaveArenaData();
            Puts($"[LobbySpawn] Lobby spawn saved to data file");
            SendReply(p, $"✓ Lobby spawn point set at {lobbySpawnPos}! Players will teleport here on join.");
            SendReply(p, $"⚠ Test with /test_lobby_spawn to verify!");
            Puts($"[LobbySpawn] Confirmation sent to admin");
        }
        [ChatCommand("set_loser_spawn")]
        private void CmdSetLoserSpawn(BasePlayer p, string c, string[] a) {
            if(!p.IsAdmin) return;
            // Get position slightly above ground to prevent spawning in air
            RaycastHit hit;
            if (Physics.Raycast(p.transform.position + Vector3.up, Vector3.down, out hit, 10f, LayerMask.GetMask("Terrain", "World", "Construction"))) {
                loserSpawnPos = hit.point + new Vector3(0, 0.5f, 0);
                // Slightly above ground
            }
            else {
                loserSpawnPos = p.transform.position;
            }
            SaveArenaData();
            SendReply(p, $"✓ Loser spawn point set at {loserSpawnPos}! Losing team will teleport here after match.");
            Puts($"Loser spawn set to: {loserSpawnPos}");
        }
        [ChatCommand("reset_ball")]
        private void CmdResetBall(BasePlayer p, string c, string[] a) {
            if(p.IsAdmin) {
                SpawnBall();
                SendReply(p, "Ball Reset.");
            }
        }
        [ChatCommand("test_lobby_spawn")]
        private void CmdTestLobbySpawn(BasePlayer player, string command, string[] args) {
            if (!player.IsAdmin) return;
            if (lobbySpawnPos == Vector3.zero) {
                SendReply(player, "❌ Lobby spawn not set! Use /set_lobby_spawn first.");
                return;
            }
            SendReply(player, $"Testing lobby spawn teleport to {lobbySpawnPos}...");
            Puts($"[TestLobby] Admin {player.displayName} testing lobby spawn teleport");
            // Wake player if sleeping
            if (player.IsSleeping()) {
                player.EndSleeping();
                Puts($"[TestLobby] Woke up sleeping player");
            }
            // Teleport
            player.Teleport(lobbySpawnPos);
            player.ClientRPCPlayer(null, player, "ForcePositionTo", lobbySpawnPos);
            player.SendNetworkUpdateImmediate();
            SendReply(player, $"✓ Teleported to lobby spawn!");
            Puts($"[TestLobby] Teleport completed");
        }
        [ChatCommand("debug_entities")]
        private void CmdDebugEntities(BasePlayer player, string command, string[] args) {
            if (!player.IsAdmin) return;
            SendReply(player, $"=== ENTITY TIMER DEBUG ===");
            SendReply(player, $"Total tracked timers: {entityTimers.Count}");
            int index = 0;
            foreach (var kvp in entityTimers) {
                SendReply(player, $"#{index++}: ID={kvp.Key}, Timer={kvp.Value != null && !kvp.Value.Destroyed}");
                if (index >= 10) break;
                // Limit to 10 entries
            }
            Puts($"[DebugEntities] Admin requested entity timer debug - {entityTimers.Count} tracked");
        }
        [ChatCommand("rotation")]
        private void CmdRotation(BasePlayer player, string command, string[] args) {
            if (!player.IsAdmin) return;
            rotationMode = !rotationMode;
            SendReply(player, $"Rotation Mode: {(rotationMode ? "ON (2 play, 1 waits)" : "OFF (3-way battle)")}");
        }
        [ChatCommand("goal_debug")]
        private void CmdToggleDebug(BasePlayer player, string command, string[] args) {
            if (!player.IsAdmin) return;
            debugActive = !debugActive;
            if (debugTimer != null) debugTimer.Destroy();
            if (debugActive) {
                SendReply(player, "Debug ON - Showing all goals.");
                debugTimer = timer.Repeat(1.0f, 0, () => {
                    if (redGoalPos != Vector3.zero) {
                        Color col = activeGoals["red"] ? Color.red : new Color(0.5f, 0, 0, 0.3f);
                        DrawGoal(player, redGoalPos, redGoalRot, col, 1.0f);
                    }
                    if (blueGoalPos != Vector3.zero) {
                        Color col = activeGoals["blue"] ? Color.blue : new Color(0, 0, 0.5f, 0.3f);
                        DrawGoal(player, blueGoalPos, blueGoalRot, col, 1.0f);
                    }
                    if (blackGoalPos1 != Vector3.zero) {
                        Color col = activeGoals["black1"] ? Color.black : new Color(0.2f, 0.2f, 0.2f, 0.3f);
                        DrawGoal(player, blackGoalPos1, blackGoalRot1, col, 1.0f);
                    }
                    if (blackGoalPos2 != Vector3.zero) {
                        Color col = activeGoals["black2"] ? Color.black : new Color(0.2f, 0.2f, 0.2f, 0.3f);
                        DrawGoal(player, blackGoalPos2, blackGoalRot2, col, 1.0f);
                    }
                }
                );
            }
            else SendReply(player, "Debug OFF.");
        }
        [ChatCommand("setskin")]
        private void CmdSetSkin(BasePlayer player, string command, string[] args) {
            if (!player.IsAdmin) return;
            if (args.Length < 3) {
                SendReply(player, "Usage: /setskin <team> <item> <skinId>");
                SendReply(player, "Teams: blue, red, black");
                SendReply(player, "Items: tshirt, pants, torso, facemask, weapon, goaliepants, goaliejacket, goalieweapon");
                SendReply(player, "Example: /setskin blue tshirt 123456789");
                return;
            }
            string team = args[0].ToLower();
            string item = args[1].ToLower();
            if (!ulong.TryParse(args[2], out ulong skinId)) {
                SendReply(player, "Invalid skin ID. Must be a number.");
                return;
            }
            if (!teamSkins.ContainsKey(team)) {
                SendReply(player, "Invalid team. Use: blue, red, or black");
                return;
            }
            var skins = teamSkins[team];
            switch (item) {
                case "tshirt" : skins.TshirtSkin = skinId;
                break;
                case "pants" : skins.PantsSkin = skinId;
                break;
                case "torso" : skins.TorsoSkin = skinId;
                break;
                case "facemask" : skins.FacemaskSkin = skinId;
                break;
                case "weapon" : skins.WeaponSkin = skinId;
                break;
                case "goaliepants" : skins.GoaliePantsSkin = skinId;
                break;
                case "goaliejacket" : skins.GoalieJacketSkin = skinId;
                break;
                case "goalieweapon" : skins.GoalieWeaponSkin = skinId;
                break;
                default : SendReply(player, "Invalid item name.");
                return;
            }
            SendReply(player, $"Set {team} {item} skin to {skinId}");
        }
        [ChatCommand("showskins")]
        private void CmdShowSkins(BasePlayer player, string command, string[] args) {
            if (!player.IsAdmin) return;
            SendReply(player, "=== TEAM SKIN IDs ===");
            foreach (var kvp in teamSkins) {
                var team = kvp.Key;
                var skins = kvp.Value;
                SendReply(player, $"--- {team.ToUpper()} ---");
                SendReply(player, $"Tshirt: {skins.TshirtSkin}");
                SendReply(player, $"Pants: {skins.PantsSkin}");
                SendReply(player, $"Torso: {skins.TorsoSkin}");
                SendReply(player, $"Facemask: {skins.FacemaskSkin}");
                SendReply(player, $"Thompson: {skins.WeaponSkin}");
                SendReply(player, $"Goalie Pants: {skins.GoaliePantsSkin}");
                SendReply(player, $"Goalie Jacket: {skins.GoalieJacketSkin}");
                SendReply(player, $"Goalie SPAS-12: {skins.GoalieWeaponSkin}");
            }
        }
        // ==========================================
        // 5. JOINING & TEAMS
        // ==========================================
        [ChatCommand("help")]
        private void CmdHelp(BasePlayer player, string command, string[] args) {
            ShowHelpMenu(player);
        }
        [ChatCommand("commands")]
        private void CmdCommands(BasePlayer player, string command, string[] args) {
            ShowHelpMenu(player);
        }
        private void ShowHelpMenu(BasePlayer player) {
            SendReply(player, "═══════════════════════════════════");
            SendReply(player, "DEATHMATCH SOCCER - COMMANDS");
            SendReply(player, "═══════════════════════════════════");
            SendReply(player, "--- PLAYER COMMANDS ---");
            SendReply(player, "/join [team] - Join a team (blue/red/black) or show team select UI");
            SendReply(player, "/leave - Leave your current team and return to lobby");
            SendReply(player, "/teams - Show team selection UI");
            SendReply(player, "/help or /commands - Show this help menu");
            if (player.IsAdmin) {
                SendReply(player, "\n--- ADMIN COMMANDS - Setup ---");
                SendReply(player, "/set_red - Set red team goal position");
                SendReply(player, "/set_blue - Set blue team goal position");
                SendReply(player, "/set_black1 - Set black goal 1 (at red position)");
                SendReply(player, "/set_black2 - Set black goal 2 (at blue position)");
                SendReply(player, "/set_center - Set ball spawn position");
                SendReply(player, "/set_lobby_spawn - Set lobby spawn point");
                SendReply(player, "/set_loser_spawn - Set spawn for losing team");
                SendReply(player, "/goal_size <width> <height> <depth> - Set goal dimensions");
                SendReply(player, "\n--- ADMIN COMMANDS - Match Control ---");
                SendReply(player, "/start_match - Start the match");
                SendReply(player, "/reset_ball - Reset ball to center");
                SendReply(player, "/rotation - Toggle rotation mode ON/OFF");
                SendReply(player, "\n--- ADMIN COMMANDS - Data ---");
                SendReply(player, "/save_goals - Save arena configuration");
                SendReply(player, "/load_goals - Reload arena configuration");
                SendReply(player, "\n--- ADMIN COMMANDS - Skins ---");
                SendReply(player, "/setskin <team> <item> <skinId> - Set team skin");
                SendReply(player, "/showskins - Display all team skins");
                SendReply(player, "\n--- ADMIN COMMANDS - Debug ---");
                SendReply(player, "/test_lobby_spawn - Test lobby spawn teleport");
                SendReply(player, "/debug_entities - Show tracked entity timers");
                SendReply(player, "/goal_debug - Toggle goal zone visualization");
            }
            SendReply(player, "═══════════════════════════════════");
        }
        [ChatCommand("teams")]
        private void CmdTeams(BasePlayer player, string command, string[] args) {
            ShowTeamSelectUI(player);
        }
        [ChatCommand("join")]
        private void CmdJoin(BasePlayer player, string command, string[] args) {
            if (args.Length == 0) {
                ShowTeamSelectUI(player);
                return;
            }
            string team = args[0].ToLower();
            redTeam.Remove(player.userID);
            blueTeam.Remove(player.userID);
            blackTeam.Remove(player.userID);
            playerRoles.Remove(player.userID);
            ballRangeState.Remove(player.userID);
            CuiHelper.DestroyUi(player, "BallRangeHUD");
            CuiHelper.DestroyUi(player, "LeashHUD");
            CuiHelper.DestroyUi(player, "TeamSelectUI");
            if (team == "red") {
                redTeam.Add(player.userID);
                CheckRole(player, "red");
            }
            else if (team == "blue") {
                blueTeam.Add(player.userID);
                CheckRole(player, "blue");
            }
            else if (team == "black") {
                blackTeam.Add(player.userID);
                CheckRole(player, "black");
            }
            else SendReply(player, "Invalid team. Use: blue, red, or black");
        }
        [ChatCommand("leave")]
        private void CmdLeave(BasePlayer player, string command, string[] args) {
            // Remove player from all teams
            bool wasOnTeam = redTeam.Remove(player.userID) || blueTeam.Remove(player.userID) || blackTeam.Remove(player.userID);
            if (!wasOnTeam) {
                SendReply(player, "You are not on any team.");
                return;
            }
            // Clean up player data
            playerRoles.Remove(player.userID);
            ballRangeState.Remove(player.userID);
            // Clean up UI
            CuiHelper.DestroyUi(player, "BallRangeHUD");
            CuiHelper.DestroyUi(player, "LeashHUD");
            CuiHelper.DestroyUi(player, "TeamSelectUI");
            CuiHelper.DestroyUi(player, "RoleSelectUI");
            CuiHelper.DestroyUi(player, "SoccerScoreboard");
            CuiHelper.DestroyUi(player, "SoccerTicker");
            // Strip inventory
            player.inventory.Strip();
            // Teleport to lobby if set
            if (lobbySpawnPos != Vector3.zero) {
                Puts($"[Leave] Player {player.displayName} left team, teleporting to lobby");
                // Wake player if sleeping
                if (player.IsSleeping()) {
                    player.EndSleeping();
                }
                // Teleport to lobby
                player.Teleport(lobbySpawnPos);
                player.ClientRPCPlayer(null, player, "ForcePositionTo", lobbySpawnPos);
                player.SendNetworkUpdateImmediate();
                SendReply(player, "✓ Left team and returned to lobby!");
                // Show team select UI after a moment
                timer.Once(1f, () => {
                    if (player != null && player.IsConnected) {
                        ShowTeamSelectUI(player);
                    }
                }
                );
            }
            else {
                SendReply(player, "✓ Left team! Use /join to select a new team.");
                Puts($"[Leave] Player {player.displayName} left team (no lobby spawn set)");
            }
        }
        [ConsoleCommand("select_team")]
        private void CmdSelectTeam(ConsoleSystem.Arg arg) {
            var player = arg.Player();
            if (player == null || arg.Args == null || arg.Args.Length < 1) return;
            CuiHelper.DestroyUi(player, "TeamSelectUI");
            string team = arg.Args[0].ToLower();
            redTeam.Remove(player.userID);
            blueTeam.Remove(player.userID);
            blackTeam.Remove(player.userID);
            playerRoles.Remove(player.userID);
            ballRangeState.Remove(player.userID);
            CuiHelper.DestroyUi(player, "BallRangeHUD");
            CuiHelper.DestroyUi(player, "LeashHUD");
            if (team == "red") {
                redTeam.Add(player.userID);
                CheckRole(player, "red");
            }
            else if (team == "blue") {
                blueTeam.Add(player.userID);
                CheckRole(player, "blue");
            }
            else if (team == "black") {
                blackTeam.Add(player.userID);
                CheckRole(player, "black");
            }
        }
        private void CheckRole(BasePlayer player, string team) {
            int goalies = 0;
            List<ulong> list = (team == "red") ? redTeam : (team == "blue") ? blueTeam : blackTeam;
            foreach(ulong id in list) if(playerRoles.ContainsKey(id) && playerRoles[id] == "Goalie") goalies++;
            if(goalies == 0) ShowRoleUI(player, team);
            else AssignRole(player, "Striker");
        }
        [ConsoleCommand("select_role")]
        private void CmdSelectRole(ConsoleSystem.Arg arg) {
            var player = arg.Player();
            if (player == null || arg.Args == null || arg.Args.Length < 1) return;
            CuiHelper.DestroyUi(player, "RoleSelectUI");
            AssignRole(player, arg.Args[0]);
        }
        private void AssignRole(BasePlayer player, string role) {
            playerRoles[player.userID] = role;
            if (centerPos != Vector3.zero) {
                Vector3 goalPos;
                Quaternion goalRot;
                if (redTeam.Contains(player.userID)) {
                    goalPos = redGoalPos;
                    goalRot = redGoalRot;
                }
                else if (blueTeam.Contains(player.userID)) {
                    goalPos = blueGoalPos;
                    goalRot = blueGoalRot;
                }
                else  // Black team
                {
                    // Determine which black goal position to use
                    if (activeGoals["black1"]) {
                        goalPos = blackGoalPos1;
                        goalRot = blackGoalRot1;
                    }
                    else if (activeGoals["black2"]) {
                        goalPos = blackGoalPos2;
                        goalRot = blackGoalRot2;
                    }
                    else {
                        // Default to black1 if neither is active (shouldn't happen in normal gameplay)
                        goalPos = blackGoalPos1 != Vector3.zero ? blackGoalPos1 : blackGoalPos2;
                        goalRot = blackGoalPos1 != Vector3.zero ? blackGoalRot1 : blackGoalRot2;
                    }
                }
                Vector3 spawn = goalPos + (goalRot * Vector3.forward * 5f);
                player.Teleport(spawn);
            }
            GiveKit(player, role);
            UpdateScoreUI(player);
        }
        // ==========================================
        // 6. KILL FEED SYSTEM
        // ==========================================
        // Funny R-rated kill messages
        private List<string> GetFunnyKillMessages() {
            return new List<string> {
                "just got absolutely demolished", "ate shit hard", "got their ass handed to them", "was fucking obliterated", "got sent to the shadow realm", "was turned into Swiss cheese", "got their skull cracked open", "got completely wrecked", "was murdered in cold blood", "got absolutely destroyed", "got their face rearranged", "was brutally executed", "got dumpstered", "was sent back to spawn", "got clapped", "got absolutely violated", "was sent to the afterlife", "got fucking annihilated", "got their head taken off", "was erased from existence"
            }
            ;
        }
        // Add kill to feed
        private void AddKillToFeed(BasePlayer killer, BasePlayer victim, string deathType = "Player") {
            if (victim == null) return;
            Puts($"[KillFeed] Adding kill feed entry for {victim.displayName}, death type: {deathType}");
            string killerName = "Unknown";
            string killerTeam = "";
            string victimTeam = redTeam.Contains(victim.userID) ? "red" : blueTeam.Contains(victim.userID) ? "blue" : blackTeam.Contains(victim.userID) ? "black" : "";
            string message = "";
            // Determine message based on death type
            if (deathType == "Fall") {
                // Fall damage messages
                var fallMessages = new List<string> {
                    "fell to their death", "forgot how to land", "faceplanted from a great height", "learned about gravity the hard way", "took the express route down", "forgot their parachute", "went splat"
                }
                ;
                message = fallMessages[UnityEngine.Random.Range(0, fallMessages.Count)];
                killerName = "Fall Damage";
            }
            else if (deathType == "Suicide") {
                // Suicide messages
                var suicideMessages = new List<string> {
                    "took the easy way out", "said fuck this shit", "quit the game", "removed themselves from the match", "rage quit IRL"
                }
                ;
                message = suicideMessages[UnityEngine.Random.Range(0, suicideMessages.Count)];
                killerName = "Suicide";
            }
            else if (deathType == "Unknown") {
                // Environmental/unknown death messages
                var unknownMessages = new List<string> {
                    "died somehow", "got rekt by the environment", "ceased to exist", "had a mysterious accident", "got deleted from reality"
                }
                ;
                message = unknownMessages[UnityEngine.Random.Range(0, unknownMessages.Count)];
                killerName = "The Environment";
            }
            else if (killer != null) {
                // Player kill messages
                killerName = killer.displayName;
                killerTeam = redTeam.Contains(killer.userID) ? "red" : blueTeam.Contains(killer.userID) ? "blue" : "black";
                var messages = GetFunnyKillMessages();
                message = messages[UnityEngine.Random.Range(0, messages.Count)];
            }
            else {
                // Fallback
                message = "died";
                killerName = "Unknown";
            }
            Puts($"[KillFeed] Killer: {killerName}, Victim: {victim.displayName}, Message: {m
// ... [Code preview truncated due to length. Full source in attached file] ...

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