Advertisement
advocaite

chess plugin

Jun 16th, 2025
267
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
C# 69.79 KB | None | 0 0
  1. using System;
  2. using System.Collections.Generic;
  3. using System.Text;
  4. using Oxide.Core;
  5. using Oxide.Core.Libraries.Covalence;
  6. using Oxide.Core.Plugins;
  7. using Rust;
  8. using Newtonsoft.Json;
  9. using UnityEngine;
  10. using Oxide.Game.Rust.Cui;
  11. using System.Linq;
  12.  
  13. namespace Oxide.Plugins
  14. {
  15.     [Info("ChessPlugin", "GrndThftJono & Grok", "1.2.2")]
  16.     [Description("A chess game integrated into Rust with a clickable GUI and spectator mode.")]
  17.     public class ChessPlugin : CSharpPlugin
  18.     {
  19.         private Dictionary<string, ChessGame> games = new Dictionary<string, ChessGame>();
  20.         private Dictionary<string, PlayerStats> playerStats = new Dictionary<string, PlayerStats>();
  21.         private const string DataFilePath = "ChessPlugin/games";
  22.         private const string StatsFilePath = "ChessPlugin/playerstats";
  23.         private const string PlayerPermission = "chessplugin.use";
  24.         private const string AdminPermission = "chessplugin.admin";
  25.         private readonly Dictionary<string, string> playerGUIs = new Dictionary<string, string>();
  26.         private Configuration config;
  27.         private bool debugMode = true;
  28.         private readonly Dictionary<string, float> lastClickTimes = new Dictionary<string, float>(); // For click debouncing
  29.  
  30.         [PluginReference] private Plugin Economics, ServerRewards;
  31.  
  32.         private class Configuration
  33.         {
  34.             public Dictionary<string, string> Colors { get; set; } = new Dictionary<string, string>
  35.             {
  36.                 { "Background", "0.1 0.1 0.1 0.8" },
  37.                 { "LightSquare", "0.8 0.8 0.8 1" },
  38.                 { "DarkSquare", "0.4 0.4 0.4 1" },
  39.                 { "Highlight", "0 1 0 0.5" },
  40.                 { "Selected", "1 1 0 0.5" },
  41.                 { "Button", "0.2 0.5 0.2 0.8" },
  42.                 { "ButtonClose", "0.5 0.2 0.2 0.8" },
  43.                 { "UndoLabel", "1 1 0 1" },
  44.                 { "UndoActiveLabel", "0 1 0 1" }
  45.             };
  46.         }
  47.  
  48.         private enum PieceType
  49.         {
  50.             Empty,
  51.             Pawn,
  52.             Knight,
  53.             Bishop,
  54.             Rook,
  55.             Queen,
  56.             King
  57.         }
  58.  
  59.         private class Piece
  60.         {
  61.             public PieceType Type { get; set; }
  62.             public bool IsWhite { get; set; }
  63.         }
  64.  
  65.         private class KingPositions
  66.         {
  67.             public int WhiteRow { get; set; }
  68.             public int WhiteCol { get; set; }
  69.             public int BlackRow { get; set; }
  70.             public int BlackCol { get; set; }
  71.         }
  72.  
  73.         private class LastMove
  74.         {
  75.             public int RowFrom { get; set; }
  76.             public int ColFrom { get; set; }
  77.             public int RowTo { get; set; }
  78.             public int ColTo { get; set; }
  79.             public Piece MovedPiece { get; set; }
  80.             public Piece CapturedPiece { get; set; }
  81.             public (bool WhiteKingside, bool WhiteQueenside, bool BlackKingside, bool BlackQueenside) CastlingBeforeMove { get; set; }
  82.             public (int Row, int Col)? EnPassantBeforeMove { get; set; }
  83.             public bool WasCastling { get; set; }
  84.             public bool WasEnPassant { get; set; }
  85.             public bool WhiteToMoveBefore { get; set; }
  86.         }
  87.  
  88.         private class ChessGame
  89.         {
  90.             public Piece[,] Board { get; set; } = new Piece[8, 8];
  91.             public bool WhiteToMove { get; set; } = true;
  92.             public (bool WhiteKingside, bool WhiteQueenside, bool BlackKingside, bool BlackQueenside) Castling { get; set; } = (true, true, true, true);
  93.             public (int Row, int Col)? EnPassant { get; set; }
  94.             public KingPositions KingPos { get; set; } = new KingPositions { WhiteRow = 0, WhiteCol = 4, BlackRow = 7, BlackCol = 4 };
  95.             public string BluePlayerId { get; set; }
  96.             public string RedPlayerId { get; set; }
  97.             public (int Row, int Col)? SelectedSquare { get; set; }
  98.             public HashSet<(int Row, int Col)> LegalMoves { get; set; } = new HashSet<(int Row, int Col)>();
  99.             public bool IsCastlingSelected { get; set; } = false;
  100.             public string Status { get; set; } = "Waiting for players";
  101.             public string GameId { get; set; }
  102.             public HashSet<string> SpectatorIds { get; set; } = new HashSet<string>();
  103.             public bool IsSolo { get; set; } = false;
  104.             public bool GameEnded { get; set; } = false;
  105.             public LastMove LastMove { get; set; }
  106.             public bool UndoRequested { get; set; } = false;
  107.             public bool UndoUsedThisTurn { get; set; } = false;
  108.             public int LastUndoTurn { get; set; } = -1;
  109.             public int TurnNumber { get; set; } = 0;
  110.  
  111.             public ChessGame(string gameId)
  112.             {
  113.                 GameId = gameId;
  114.                 InitializeBoard();
  115.             }
  116.  
  117.             private void InitializeBoard()
  118.             {
  119.                 for (int i = 0; i < 8; i++)
  120.                     for (int j = 0; j < 8; j++)
  121.                         Board[i, j] = new Piece { Type = PieceType.Empty };
  122.  
  123.                 for (int i = 0; i < 8; i++)
  124.                 {
  125.                     Board[1, i] = new Piece { Type = PieceType.Pawn, IsWhite = true };
  126.                     Board[6, i] = new Piece { Type = PieceType.Pawn, IsWhite = false };
  127.                 }
  128.  
  129.                 PieceType[] backRank = { PieceType.Rook, PieceType.Knight, PieceType.Bishop, PieceType.Queen, PieceType.King, PieceType.Bishop, PieceType.Knight, PieceType.Rook };
  130.                 for (int i = 0; i < 8; i++)
  131.                 {
  132.                     Board[0, i] = new Piece { Type = backRank[i], IsWhite = true };
  133.                     Board[7, i] = new Piece { Type = backRank[i], IsWhite = false };
  134.                 }
  135.  
  136.                 KingPos.WhiteRow = 0;
  137.                 KingPos.WhiteCol = 4;
  138.                 KingPos.BlackRow = 7;
  139.                 KingPos.BlackCol = 4;
  140.             }
  141.         }
  142.  
  143.         private class PlayerStats
  144.         {
  145.             public int Wins { get; set; }
  146.             public int Losses { get; set; }
  147.             public int Draws { get; set; }
  148.         }
  149.  
  150.         private void Init()
  151.         {
  152.             permission.RegisterPermission(PlayerPermission, this);
  153.             permission.RegisterPermission(AdminPermission, this);
  154.             LoadConfig();
  155.             LoadData();
  156.             LoadStats();
  157.             RegisterCommands();
  158.             Puts("ChessPlugin loaded.");
  159.         }
  160.  
  161.         private void Unload()
  162.         {
  163.             foreach (var player in BasePlayer.activePlayerList)
  164.                 DestroyGUI(player);
  165.             SaveData();
  166.             SaveStats();
  167.         }
  168.  
  169.         protected override void LoadConfig()
  170.         {
  171.             base.LoadConfig();
  172.             try
  173.             {
  174.                 config = Config.ReadObject<Configuration>();
  175.                 if (config == null)
  176.                 {
  177.                     LogDebug("Configuration file not found, creating new one.");
  178.                     config = new Configuration();
  179.                     SaveConfig();
  180.                 }
  181.             }
  182.             catch (Exception ex)
  183.             {
  184.                 Puts($"Error loading configuration: {ex.Message}. Creating default configuration.");
  185.                 config = new Configuration();
  186.                 SaveConfig();
  187.             }
  188.         }
  189.  
  190.         protected override void SaveConfig()
  191.         {
  192.             try
  193.             {
  194.                 Config.WriteObject(config);
  195.                 LogDebug("Configuration saved.");
  196.             }
  197.             catch (Exception ex)
  198.             {
  199.                 Puts($"Error saving configuration: {ex.Message}");
  200.             }
  201.         }
  202.  
  203.         private void LoadData()
  204.         {
  205.             try
  206.             {
  207.                 games = Interface.Oxide.DataFileSystem.ReadObject<Dictionary<string, ChessGame>>(DataFilePath);
  208.                 if (games == null)
  209.                 {
  210.                     LogDebug("Chess game data not found, initializing empty games.");
  211.                     games = new Dictionary<string, ChessGame>();
  212.                     SaveData();
  213.                 }
  214.                 Puts($"Loaded {games.Count} chess games.");
  215.             }
  216.             catch (Exception ex)
  217.             {
  218.                 Puts($"Error loading chess data: {ex.Message}. Initializing empty games.");
  219.                 games = new Dictionary<string, ChessGame>();
  220.                 SaveData();
  221.             }
  222.         }
  223.  
  224.         private void SaveData()
  225.         {
  226.             try
  227.             {
  228.                 Interface.Oxide.DataFileSystem.WriteObject(DataFilePath, games);
  229.                 LogDebug("Saved chess game data.");
  230.             }
  231.             catch (Exception ex)
  232.             {
  233.                 Puts($"Error saving chess data: {ex.Message}");
  234.             }
  235.         }
  236.  
  237.         private void LoadStats()
  238.         {
  239.             try
  240.             {
  241.                 playerStats = Interface.Oxide.DataFileSystem.ReadObject<Dictionary<string, PlayerStats>>(StatsFilePath);
  242.                 if (playerStats == null)
  243.                 {
  244.                     LogDebug("Player stats data not found, initializing empty stats.");
  245.                     playerStats = new Dictionary<string, PlayerStats>();
  246.                     SaveStats();
  247.                 }
  248.                 Puts($"Loaded {$"{playerStats.Count} player stats"}.");
  249.             }
  250.             catch (Exception ex)
  251.             {
  252.                 Puts($"Error loading player stats: {ex.Message}. Initializing empty stats.");
  253.                 playerStats = new Dictionary<string, PlayerStats>();
  254.                 SaveStats();
  255.             }
  256.         }
  257.  
  258.         private void SaveStats()
  259.         {
  260.             try
  261.             {
  262.                 Interface.Oxide.DataFileSystem.WriteObject(StatsFilePath, playerStats);
  263.                 LogDebug("Saved player stats data.");
  264.             }
  265.             catch (Exception ex)
  266.             {
  267.                 Puts($"Error saving player stats: {ex.Message}");
  268.             }
  269.         }
  270.  
  271.         private void LogDebug(string message)
  272.         {
  273.             if (debugMode)
  274.                 Puts($"[DEBUG] {message}");
  275.         }
  276.  
  277.         private HashSet<(int Row, int Col)> GetLegalMoves(ChessGame game, int row, int col)
  278.         {
  279.             var moves = new HashSet<(int Row, int Col)>();
  280.             var piece = game.Board[row, col];
  281.             if (piece.Type == PieceType.Empty || piece.IsWhite != game.WhiteToMove)
  282.             {
  283.                 LogDebug($"No legal moves: Empty or wrong color at {row},{col}");
  284.                 return moves;
  285.             }
  286.  
  287.             bool inCheck = IsInCheck(game, game.WhiteToMove);
  288.             LogDebug($"Checking moves for {piece.Type} at {row},{col}. In check: {inCheck}");
  289.  
  290.             var tempMoves = new HashSet<(int Row, int Col)>();
  291.  
  292.             switch (piece.Type)
  293.             {
  294.                 case PieceType.Pawn:
  295.                     int direction = piece.IsWhite ? 1 : -1;
  296.                     int startRow = piece.IsWhite ? 1 : 6;
  297.                     int newRow = row + direction;
  298.                     if (newRow >= 0 && newRow < 8 && game.Board[newRow, col].Type == PieceType.Empty)
  299.                     {
  300.                         tempMoves.Add((newRow, col));
  301.                         if (row == startRow && game.Board[newRow + direction, col].Type == PieceType.Empty)
  302.                             tempMoves.Add((newRow + direction, col));
  303.                     }
  304.                     foreach (int dc in new int[] { -1, 1 })
  305.                     {
  306.                         int newCol = col + dc;
  307.                         if (newRow >= 0 && newRow < 8 && newCol >= 0 && newCol < 8)
  308.                         {
  309.                             if (game.Board[newRow, newCol].Type != PieceType.Empty && game.Board[newRow, newCol].IsWhite != piece.IsWhite)
  310.                                 tempMoves.Add((newRow, newCol));
  311.                             if (game.EnPassant == (newRow, newCol))
  312.                                 tempMoves.Add((newRow, newCol));
  313.                         }
  314.                     }
  315.                     break;
  316.                 case PieceType.Knight:
  317.                     var knightOffsets = new (int, int)[] { (-2, -1), (-2, 1), (-1, -2), (-1, 2), (1, -2), (1, 2), (2, -1), (2, 1) };
  318.                     foreach (var (dr, dc) in knightOffsets)
  319.                     {
  320.                         int r = row + dr, c = col + dc;
  321.                         if (r >= 0 && r < 8 && c >= 0 && c < 8 && (game.Board[r, c].Type == PieceType.Empty || game.Board[r, c].IsWhite != piece.IsWhite))
  322.                             tempMoves.Add((r, c));
  323.                     }
  324.                     break;
  325.                 case PieceType.Bishop:
  326.                     AddSlidingMoves(game, tempMoves, row, col, piece.IsWhite, new (int, int)[] { (1, 1), (1, -1), (-1, 1), (-1, -1) });
  327.                     break;
  328.                 case PieceType.Rook:
  329.                     AddSlidingMoves(game, tempMoves, row, col, piece.IsWhite, new (int, int)[] { (1, 0), (0, 1), (-1, 0), (0, -1) });
  330.                     break;
  331.                 case PieceType.Queen:
  332.                     AddSlidingMoves(game, tempMoves, row, col, piece.IsWhite, new (int, int)[] { (1, 0), (0, 1), (-1, 0), (0, -1), (1, 1), (1, -1), (-1, 1), (-1, -1) });
  333.                     break;
  334.                 case PieceType.King:
  335.                     var kingOffsets = new (int, int)[] { (1, 0), (0, 1), (-1, 0), (0, -1), (1, 1), (1, -1), (-1, 1), (-1, -1) };
  336.                     foreach (var (dr, dc) in kingOffsets)
  337.                     {
  338.                         int r = row + dr, c = col + dc;
  339.                         if (r >= 0 && r < 8 && c >= 0 && c < 8 && (game.Board[r, c].Type == PieceType.Empty || game.Board[r, c].IsWhite != piece.IsWhite))
  340.                             tempMoves.Add((r, c));
  341.                     }
  342.                     if (!inCheck)
  343.                     {
  344.                         if (piece.IsWhite)
  345.                         {
  346.                             if (game.Castling.WhiteKingside && game.Board[0, 5].Type == PieceType.Empty && game.Board[0, 6].Type == PieceType.Empty)
  347.                                 tempMoves.Add((0, 6));
  348.                             if (game.Castling.WhiteQueenside && game.Board[0, 1].Type == PieceType.Empty && game.Board[0, 2].Type == PieceType.Empty && game.Board[0, 3].Type == PieceType.Empty)
  349.                                 tempMoves.Add((0, 2));
  350.                         }
  351.                         else
  352.                         {
  353.                             if (game.Castling.BlackKingside && game.Board[7, 5].Type == PieceType.Empty && game.Board[7, 6].Type == PieceType.Empty)
  354.                                 tempMoves.Add((7, 6));
  355.                             if (game.Castling.BlackQueenside && game.Board[7, 1].Type == PieceType.Empty && game.Board[7, 2].Type == PieceType.Empty && game.Board[7, 3].Type == PieceType.Empty)
  356.                                 tempMoves.Add((7, 2));
  357.                         }
  358.                     }
  359.                     break;
  360.             }
  361.  
  362.             foreach (var move in tempMoves)
  363.             {
  364.                 if (IsLegalMove(game, row, col, move.Row, move.Col))
  365.                 {
  366.                     moves.Add(move);
  367.                     LogDebug($"Legal move for {piece.Type} from {row},{col} to {move.Row},{move.Col}");
  368.                 }
  369.                 else
  370.                 {
  371.                     LogDebug($"Illegal move for {piece.Type} from {row},{col} to {move.Row},{move.Col} (leaves king in check)");
  372.                 }
  373.             }
  374.  
  375.             return moves;
  376.         }
  377.  
  378.         private bool IsLegalMove(ChessGame game, int rowFrom, int colFrom, int rowTo, int colTo)
  379.         {
  380.             var originalPiece = game.Board[rowFrom, colFrom];
  381.             var targetPiece = game.Board[rowTo, colTo];
  382.             if (targetPiece.Type != PieceType.Empty && targetPiece.IsWhite == originalPiece.IsWhite)
  383.             {
  384.                 LogDebug($"Invalid move: Cannot capture own piece at {rowTo},{colTo} from {rowFrom},{colFrom}");
  385.                 return false;
  386.             }
  387.  
  388.             var originalEnPassant = game.EnPassant;
  389.             var originalKingPos = new KingPositions
  390.             {
  391.                 WhiteRow = game.KingPos.WhiteRow,
  392.                 WhiteCol = game.KingPos.WhiteCol,
  393.                 BlackRow = game.KingPos.BlackRow,
  394.                 BlackCol = game.KingPos.BlackCol
  395.             };
  396.             var originalCastling = game.Castling;
  397.  
  398.             game.Board[rowTo, colTo] = originalPiece;
  399.             game.Board[rowFrom, colFrom] = new Piece { Type = PieceType.Empty };
  400.  
  401.             if (originalPiece.Type == PieceType.King)
  402.             {
  403.                 bool isCastling = (rowFrom == rowTo) && (colFrom == 4) && (colTo == 6 || colTo == 2);
  404.                 if (isCastling)
  405.                 {
  406.                     int rookFromCol = colTo == 6 ? 7 : 0;
  407.                     int rookToCol = colTo == 6 ? 5 : 3;
  408.                     var rookPiece = game.Board[rowTo, rookFromCol];
  409.                     if (rookPiece == null || rookPiece.Type != PieceType.Rook || rookPiece.IsWhite != originalPiece.IsWhite)
  410.                     {
  411.                         LogDebug($"Invalid castling: No valid rook at {rowTo},{rookFromCol}");
  412.                         game.Board[rowFrom, colFrom] = originalPiece;
  413.                         game.Board[rowTo, colTo] = targetPiece;
  414.                         return false;
  415.                     }
  416.                     game.Board[rowTo, rookToCol] = rookPiece;
  417.                     game.Board[rowTo, rookFromCol] = new Piece { Type = PieceType.Empty };
  418.                 }
  419.  
  420.                 if (originalPiece.IsWhite)
  421.                 {
  422.                     game.KingPos.WhiteRow = rowTo;
  423.                     game.KingPos.WhiteCol = colTo;
  424.                     game.Castling = (false, false, game.Castling.BlackKingside, game.Castling.BlackQueenside);
  425.                 }
  426.                 else
  427.                 {
  428.                     game.KingPos.BlackRow = rowTo;
  429.                     game.KingPos.BlackCol = colTo;
  430.                     game.Castling = (game.Castling.WhiteKingside, game.Castling.WhiteQueenside, false, false);
  431.                 }
  432.             }
  433.             else if (originalPiece.Type == PieceType.Pawn)
  434.             {
  435.                 if ((originalPiece.IsWhite && rowTo == 7) || (!originalPiece.IsWhite && rowTo == 0))
  436.                     game.Board[rowTo, colTo] = new Piece { Type = PieceType.Queen, IsWhite = originalPiece.IsWhite };
  437.                 game.EnPassant = null;
  438.                 if (Math.Abs(rowFrom - rowTo) == 2)
  439.                     game.EnPassant = (rowFrom + (originalPiece.IsWhite ? 1 : -1), colFrom);
  440.                 else if (originalEnPassant == (rowTo, colTo))
  441.                     game.Board[originalPiece.IsWhite ? rowTo - 1 : rowTo + 1, colTo] = new Piece { Type = PieceType.Empty };
  442.             }
  443.             else if (originalPiece.Type == PieceType.Rook)
  444.             {
  445.                 if (originalPiece.IsWhite)
  446.                 {
  447.                     if (rowFrom == 0 && colFrom == 0)
  448.                         game.Castling = (game.Castling.WhiteKingside, false, game.Castling.BlackKingside, game.Castling.BlackQueenside);
  449.                     else if (rowFrom == 0 && colFrom == 7)
  450.                         game.Castling = (false, game.Castling.WhiteQueenside, game.Castling.BlackKingside, game.Castling.BlackQueenside);
  451.                 }
  452.                 else
  453.                 {
  454.                     if (rowFrom == 7 && colFrom == 0)
  455.                         game.Castling = (game.Castling.WhiteKingside, game.Castling.WhiteQueenside, game.Castling.BlackKingside, false);
  456.                     else if (rowFrom == 7 && colFrom == 7)
  457.                         game.Castling = (game.Castling.WhiteKingside, game.Castling.WhiteQueenside, false, game.Castling.BlackQueenside);
  458.                 }
  459.             }
  460.  
  461.             bool isInCheck = IsInCheck(game, originalPiece.IsWhite);
  462.  
  463.             game.Board[rowFrom, colFrom] = originalPiece;
  464.             game.Board[rowTo, colTo] = targetPiece;
  465.             game.EnPassant = originalEnPassant;
  466.             game.KingPos = originalKingPos;
  467.             game.Castling = originalCastling;
  468.  
  469.             return !isInCheck;
  470.         }
  471.  
  472.         private bool MakeMove(ChessGame game, int rowFrom, int colFrom, int rowTo, int colTo)
  473.         {
  474.             try
  475.             {
  476.                 var piece = game.Board[rowFrom, colFrom];
  477.                 if (piece.Type == PieceType.Empty || piece.IsWhite != game.WhiteToMove)
  478.                 {
  479.                     LogDebug($"Invalid move attempt in game {game.GameId}: {rowFrom},{colFrom} to {rowTo},{colTo} (empty or wrong color)");
  480.                     return false;
  481.                 }
  482.  
  483.                 if (!game.LegalMoves.Contains((rowTo, colTo)))
  484.                 {
  485.                     LogDebug($"Illegal move in game {game.GameId}: {rowFrom},{colFrom} to {rowTo},{colTo} (not in legal moves)");
  486.                     return false;
  487.                 }
  488.  
  489.                 var targetPiece = game.Board[rowTo, colTo];
  490.                 bool isCastling = piece.Type == PieceType.King && rowFrom == rowTo && colFrom == 4 && (colTo == 6 || colTo == 2);
  491.                 bool isEnPassant = piece.Type == PieceType.Pawn && game.EnPassant == (rowTo, colTo);
  492.  
  493.                 var lastMove = new LastMove
  494.                 {
  495.                     RowFrom = rowFrom,
  496.                     ColFrom = colFrom,
  497.                     RowTo = rowTo,
  498.                     ColTo = colTo,
  499.                     MovedPiece = new Piece { Type = piece.Type, IsWhite = piece.IsWhite },
  500.                     CapturedPiece = targetPiece.Type != PieceType.Empty ? new Piece { Type = targetPiece.Type, IsWhite = targetPiece.IsWhite } : null,
  501.                     CastlingBeforeMove = game.Castling,
  502.                     EnPassantBeforeMove = game.EnPassant,
  503.                     WasCastling = isCastling,
  504.                     WasEnPassant = isEnPassant,
  505.                     WhiteToMoveBefore = game.WhiteToMove
  506.                 };
  507.  
  508.                 if (targetPiece.Type == PieceType.King)
  509.                 {
  510.                     game.GameEnded = true;
  511.                     game.Status = piece.IsWhite ? "Blue wins!" : "Red wins!";
  512.                     game.LastMove = lastMove;
  513.                     RecordGameResult(game, piece.IsWhite ? "Blue" : "Red");
  514.                     ShowBannerToAll($"Game {game.GameId}: {game.Status} (King captured!)");
  515.                     SaveData();
  516.                     return true;
  517.                 }
  518.  
  519.                 LogDebug($"Pre-move board state for game {game.GameId}:\n{DumpBoardState(game)}");
  520.  
  521.                 if (isCastling)
  522.                 {
  523.                     LogDebug($"Executing castling move in game {game.GameId}: King from {rowFrom},{colFrom} to {rowTo},{colTo}");
  524.                     int rookFromCol = colTo == 6 ? 7 : 0;
  525.                     int rookToCol = colTo == 6 ? 5 : 3;
  526.                     var rookPiece = game.Board[rowTo, rookFromCol];
  527.  
  528.                     if (rookPiece == null || rookPiece.Type != PieceType.Rook || rookPiece.IsWhite != piece.IsWhite)
  529.                     {
  530.                         LogDebug($"Invalid rook at {rowTo},{rookFromCol} for castling in game {game.GameId}");
  531.                         return false;
  532.                     }
  533.  
  534.                     // Move rook
  535.                     game.Board[rowTo, rookToCol] = rookPiece;
  536.                     game.Board[rowTo, rookFromCol] = new Piece { Type = PieceType.Empty };
  537.                     LogDebug($"Castling: Rook moved from {rowTo},{rookFromCol} to {rowTo},{rookToCol}");
  538.  
  539.                     // Move king
  540.                     game.Board[rowTo, colTo] = piece;
  541.                     game.Board[rowFrom, colFrom] = new Piece { Type = PieceType.Empty };
  542.                     LogDebug($"Castling: King moved from {rowFrom},{colFrom} to {rowTo},{colTo}");
  543.  
  544.                     if (piece.IsWhite)
  545.                     {
  546.                         game.KingPos.WhiteRow = rowTo;
  547.                         game.KingPos.WhiteCol = colTo;
  548.                         game.Castling = (false, false, game.Castling.BlackKingside, game.Castling.BlackQueenside);
  549.                     }
  550.                     else
  551.                     {
  552.                         game.KingPos.BlackRow = rowTo;
  553.                         game.KingPos.BlackCol = colTo;
  554.                         game.Castling = (game.Castling.WhiteKingside, game.Castling.WhiteQueenside, false, false);
  555.                     }
  556.                 }
  557.                 else
  558.                 {
  559.                     game.Board[rowTo, colTo] = piece;
  560.                     game.Board[rowFrom, colFrom] = new Piece { Type = PieceType.Empty };
  561.  
  562.                     if (piece.Type == PieceType.King)
  563.                     {
  564.                         if (piece.IsWhite)
  565.                         {
  566.                             game.KingPos.WhiteRow = rowTo;
  567.                             game.KingPos.WhiteCol = colTo;
  568.                             game.Castling = (false, false, game.Castling.BlackKingside, game.Castling.BlackQueenside);
  569.                         }
  570.                         else
  571.                         {
  572.                             game.KingPos.BlackRow = rowTo;
  573.                             game.KingPos.BlackCol = colTo;
  574.                             game.Castling = (game.Castling.WhiteKingside, game.Castling.WhiteQueenside, false, false);
  575.                         }
  576.                     }
  577.                     else if (piece.Type == PieceType.Rook)
  578.                     {
  579.                         if (piece.IsWhite)
  580.                         {
  581.                             if (rowFrom == 0 && colFrom == 0)
  582.                                 game.Castling = (game.Castling.WhiteKingside, false, game.Castling.BlackKingside, game.Castling.BlackQueenside);
  583.                             else if (rowFrom == 0 && colFrom == 7)
  584.                                 game.Castling = (false, game.Castling.WhiteQueenside, game.Castling.BlackKingside, game.Castling.BlackQueenside);
  585.                         }
  586.                         else
  587.                         {
  588.                             if (rowFrom == 7 && colFrom == 0)
  589.                                 game.Castling = (game.Castling.WhiteKingside, game.Castling.WhiteQueenside, game.Castling.BlackKingside, false);
  590.                             else if (rowFrom == 7 && colFrom == 7)
  591.                                 game.Castling = (game.Castling.WhiteKingside, game.Castling.WhiteQueenside, false, game.Castling.BlackQueenside);
  592.                         }
  593.                     }
  594.                     else if (piece.Type == PieceType.Pawn)
  595.                     {
  596.                         // Handle pawn promotion
  597.                         if ((piece.IsWhite && rowTo == 7) || (!piece.IsWhite && rowTo == 0))
  598.                             game.Board[rowTo, colTo] = new Piece { Type = PieceType.Queen, IsWhite = piece.IsWhite };
  599.                        
  600.                         // Handle en passant capture - remove the captured pawn
  601.                         if (isEnPassant)
  602.                         {
  603.                             int capturedPawnRow = piece.IsWhite ? rowTo - 1 : rowTo + 1;
  604.                             game.Board[capturedPawnRow, colTo] = new Piece { Type = PieceType.Empty };
  605.                             LogDebug($"En passant capture: Removed enemy pawn at {capturedPawnRow},{colTo}");
  606.                         }
  607.                        
  608.                         // Set en passant target square if pawn moved 2 squares
  609.                         if (Math.Abs(rowFrom - rowTo) == 2)
  610.                         {
  611.                             game.EnPassant = (rowFrom + (piece.IsWhite ? 1 : -1), colFrom);
  612.                             LogDebug($"En passant target set at {game.EnPassant}");
  613.                         }
  614.                     }
  615.                    
  616.                     // Clear en passant target for any non-pawn double move
  617.                     if (piece.Type != PieceType.Pawn || Math.Abs(rowFrom - rowTo) != 2)
  618.                     {
  619.                         game.EnPassant = null;
  620.                     }
  621.                 }
  622.  
  623.                 game.LastMove = lastMove;
  624.  
  625.                 LogDebug($"Post-move board state for game {game.GameId}:\n{DumpBoardState(game)}");
  626.                 if (isCastling)
  627.                     LogDebug($"Castling completed: Board[{rowTo},{(colTo == 5 ? 4 : 2)}]={game.Board[rowTo, (colTo == 5 ? 4 : 2)]?.Type}, Board[{rowTo},{(colTo == 5 ? 7 : 0)}]={game.Board[rowTo, (colTo == 5 ? 7 : 0)]?.Type}");
  628.  
  629.                 game.WhiteToMove = !game.WhiteToMove;
  630.                 game.TurnNumber++;
  631.                 game.UndoUsedThisTurn = false;
  632.                 game.IsCastlingSelected = false;
  633.                 SaveData();
  634.                 LogDebug($"Board state saved for game {game.GameId} after move");
  635.  
  636.                 if (IsCheckmate(game))
  637.                 {
  638.                     game.GameEnded = true;
  639.                     game.Status = game.WhiteToMove ? "Red wins!" : "Blue wins!";
  640.                     RecordGameResult(game, game.WhiteToMove ? "Red" : "Blue");
  641.                     ShowBannerToAll($"Game {game.GameId}: {game.Status}");
  642.                 }
  643.                 else if (IsStalemate(game))
  644.                 {
  645.                     game.GameEnded = true;
  646.                     game.Status = "Draw by stalemate!";
  647.                     RecordGameResult(game, "Draw");
  648.                     ShowBannerToAll($"Game {game.GameId}: {game.Status}");
  649.                 }
  650.                 else if (IsInsufficientMaterial(game))
  651.                 {
  652.                     game.GameEnded = true;
  653.                     game.Status = "Draw by insufficient material!";
  654.                     RecordGameResult(game, "Draw");
  655.                     ShowBannerToAll($"Game {game.GameId}: {game.Status}");
  656.                 }
  657.  
  658.                 LogDebug($"Move completed in game {game.GameId}: {rowFrom},{colFrom} to {rowTo},{colTo}");
  659.                 return true;
  660.             }
  661.             catch (Exception ex)
  662.             {
  663.                 Puts($"Error in MakeMove for game {game.GameId}: {ex.Message}");
  664.                 return false;
  665.             }
  666.         }
  667.  
  668.         private void RequestUndo(IPlayer player, ChessGame game)
  669.         {
  670.             if (game.GameEnded)
  671.             {
  672.                 player.Reply("Cannot request undo after the game has ended!");
  673.                 return;
  674.             }
  675.  
  676.             if (game.IsSolo)
  677.             {
  678.                 game.UndoRequested = true;
  679.                 SaveData();
  680.                 player.Reply("Undo request accepted (solo mode). Click 'Undo' to revert the last move.");
  681.             }
  682.             else
  683.             {
  684.                 if (game.UndoRequested)
  685.                 {
  686.                     player.Reply("An undo request is already pending!");
  687.                     return;
  688.                 }
  689.  
  690.                 game.UndoRequested = true;
  691.                 SaveData();
  692.  
  693.                 var otherPlayerId = game.WhiteToMove ? game.RedPlayerId : game.BluePlayerId;
  694.                 var otherPlayer = covalence.Players.FindPlayerById(otherPlayerId);
  695.                 if (otherPlayer != null && otherPlayer.IsConnected)
  696.                 {
  697.                     otherPlayer.Reply($"{player.Name} has requested to undo the last move. Click 'Undo Requested (Accept?)' to accept.");
  698.                 }
  699.  
  700.                 player.Reply("Undo request sent to the other player.");
  701.             }
  702.  
  703.             foreach (var p in BasePlayer.activePlayerList)
  704.             {
  705.                 if (p.UserIDString == game.BluePlayerId || p.UserIDString == game.RedPlayerId)
  706.                     ShowGUI(covalence.Players.FindPlayerById(p.UserIDString), game, false);
  707.                 else if (game.SpectatorIds.Contains(p.UserIDString))
  708.                     ShowGUI(covalence.Players.FindPlayerById(p.UserIDString), game, true);
  709.             }
  710.         }
  711.  
  712.         private void AcceptUndo(IPlayer player, ChessGame game)
  713.         {
  714.             if (!game.UndoRequested)
  715.             {
  716.                 player.Reply("No undo request is pending!");
  717.                 return;
  718.             }
  719.  
  720.             if (game.IsSolo)
  721.             {
  722.                 player.Reply("Undo requests are not needed in solo mode!");
  723.                 return;
  724.             }
  725.  
  726.             var requestingPlayerId = game.WhiteToMove ? game.RedPlayerId : game.BluePlayerId;
  727.             if (player.Id == requestingPlayerId)
  728.             {
  729.                 player.Reply("You cannot accept your own undo request!");
  730.                 return;
  731.             }
  732.  
  733.             game.LastUndoTurn = game.TurnNumber;
  734.             SaveData();
  735.  
  736.             player.Reply("Undo request accepted. The 'Undo' label is now available for one use this turn.");
  737.  
  738.             foreach (var p in BasePlayer.activePlayerList)
  739.             {
  740.                 if (p.UserIDString == game.BluePlayerId || p.UserIDString == game.RedPlayerId)
  741.                     ShowGUI(covalence.Players.FindPlayerById(p.UserIDString), game, false);
  742.                 else if (game.SpectatorIds.Contains(p.UserIDString))
  743.                     ShowGUI(covalence.Players.FindPlayerById(p.UserIDString), game, true);
  744.             }
  745.         }
  746.  
  747.         private void PerformUndo(ChessGame game, IPlayer player)
  748.         {
  749.             if (game.GameEnded)
  750.             {
  751.                 player.Reply("Cannot undo after the game has ended!");
  752.                 return;
  753.             }
  754.  
  755.             if (game.LastMove == null)
  756.             {
  757.                 player.Reply("No moves to undo!");
  758.                 return;
  759.             }
  760.  
  761.             if (!game.IsSolo && game.LastUndoTurn != game.TurnNumber)
  762.             {
  763.                 player.Reply("Undo is only available after an accepted undo request!");
  764.                 return;
  765.             }
  766.  
  767.             if (game.UndoUsedThisTurn)
  768.             {
  769.                 player.Reply("Undo has already been used this turn!");
  770.                 return;
  771.             }
  772.  
  773.             var lastMove = game.LastMove;
  774.  
  775.             game.Board[lastMove.RowFrom, lastMove.ColFrom] = lastMove.MovedPiece;
  776.             game.Board[lastMove.RowTo, lastMove.ColTo] = lastMove.CapturedPiece ?? new Piece { Type = PieceType.Empty };
  777.  
  778.             if (lastMove.WasCastling)
  779.             {
  780.                 if (lastMove.ColTo == 6) // Kingside castling: King went to g-file (6)
  781.                 {
  782.                     game.Board[lastMove.RowTo, 7] = new Piece { Type = PieceType.Rook, IsWhite = lastMove.MovedPiece.IsWhite };
  783.                     game.Board[lastMove.RowTo, 5] = new Piece { Type = PieceType.Empty };
  784.                     LogDebug($"Undoing kingside castling: Rook restored to {lastMove.RowTo},7");
  785.                 }
  786.                 else if (lastMove.ColTo == 2) // Queenside castling: King went to c-file (2)
  787.                 {
  788.                     game.Board[lastMove.RowTo, 0] = new Piece { Type = PieceType.Rook, IsWhite = lastMove.MovedPiece.IsWhite };
  789.                     game.Board[lastMove.RowTo, 3] = new Piece { Type = PieceType.Empty };
  790.                     LogDebug($"Undoing queenside castling: Rook restored to {lastMove.RowTo},0");
  791.                 }
  792.             }
  793.  
  794.             if (lastMove.WasEnPassant)
  795.             {
  796.                 game.Board[lastMove.MovedPiece.IsWhite ? lastMove.RowTo - 1 : lastMove.RowTo + 1, lastMove.ColTo] = lastMove.CapturedPiece;
  797.             }
  798.  
  799.             if (lastMove.MovedPiece.Type == PieceType.King)
  800.             {
  801.                 if (lastMove.MovedPiece.IsWhite)
  802.                 {
  803.                     game.KingPos.WhiteRow = lastMove.RowFrom;
  804.                     game.KingPos.WhiteCol = lastMove.ColFrom;
  805.                 }
  806.                 else
  807.                 {
  808.                     game.KingPos.BlackRow = lastMove.RowFrom;
  809.                     game.KingPos.BlackCol = lastMove.ColFrom;
  810.                 }
  811.             }
  812.  
  813.             game.Castling = lastMove.CastlingBeforeMove;
  814.             game.EnPassant = lastMove.EnPassantBeforeMove;
  815.             game.WhiteToMove = lastMove.WhiteToMoveBefore;
  816.             game.UndoUsedThisTurn = true;
  817.             game.LastMove = null;
  818.             game.UndoRequested = false;
  819.             game.IsCastlingSelected = false;
  820.             SaveData();
  821.  
  822.             player.Reply("Last move undone.");
  823.  
  824.             foreach (var p in BasePlayer.activePlayerList)
  825.             {
  826.                 if (p.UserIDString == game.BluePlayerId || p.UserIDString == game.RedPlayerId)
  827.                     ShowGUI(covalence.Players.FindPlayerById(p.UserIDString), game, false);
  828.                 else if (game.SpectatorIds.Contains(p.UserIDString))
  829.                     ShowGUI(covalence.Players.FindPlayerById(p.UserIDString), game, true);
  830.             }
  831.         }
  832.  
  833.         private void ShowGUI(IPlayer player, ChessGame game, bool isSpectator)
  834.         {
  835.             var basePlayer = player.Object as BasePlayer;
  836.             if (basePlayer == null) return;
  837.  
  838.             DestroyGUI(basePlayer);
  839.  
  840.             var container = new CuiElementContainer();
  841.             string panelName = $"ChessBoard_{player.Id}_{game.GameId}";
  842.             playerGUIs[player.Id] = panelName;
  843.  
  844.             container.Add(new CuiPanel
  845.             {
  846.                 Image = { Color = config.Colors["Background"] },
  847.                 RectTransform = { AnchorMin = "0.2 0.2", AnchorMax = "0.8 0.8" },
  848.                 CursorEnabled = true
  849.             }, "Overlay", panelName);
  850.  
  851.             string playerStatus = isSpectator ? "Spectating" : (game.BluePlayerId == player.Id ? "Blue" : game.RedPlayerId == player.Id ? "Red" : "Spectating");
  852.             string displayStatus = game.Status.Replace("Blue", "Blue").Replace("Red", "Red");
  853.             container.Add(new CuiLabel
  854.             {
  855.                 Text = { Text = $"Chess (Game {game.GameId}) - {displayStatus} [{playerStatus}]", FontSize = 18, Align = TextAnchor.MiddleCenter, Color = config.Colors["Highlight"] },
  856.                 RectTransform = { AnchorMin = "0.1 0.9", AnchorMax = "0.9 1.0" }
  857.             }, panelName);
  858.  
  859.             if (!isSpectator && !game.GameEnded)
  860.             {
  861.                 string instructionText = game.IsCastlingSelected ? "Select castling destination" : "Click to select and move";
  862.                 container.Add(new CuiLabel
  863.                 {
  864.                     Text = { Text = instructionText, FontSize = 12, Align = TextAnchor.MiddleCenter, Color = "1 1 1 1" },
  865.                     RectTransform = { AnchorMin = "0.1 0.85", AnchorMax = "0.9 0.9" }
  866.                 }, panelName);
  867.  
  868.                 string undoLabelText = game.UndoRequested ? "Undo Requested (Accept?)" : (game.IsSolo || game.LastUndoTurn == game.TurnNumber && !game.UndoUsedThisTurn ? "Undo" : "Request Undo");
  869.                 string undoCommand = game.UndoRequested ? $"chess acceptundo {game.GameId}" : (game.IsSolo || game.LastUndoTurn == game.TurnNumber && !game.UndoUsedThisTurn ? $"chess undo {game.GameId}" : $"chess requestundo {game.GameId}");
  870.                 string undoLabelColor = (game.IsSolo || game.LastUndoTurn == game.TurnNumber && !game.UndoUsedThisTurn) ? config.Colors["UndoActiveLabel"] : config.Colors["UndoLabel"];
  871.                 container.Add(new CuiButton
  872.                 {
  873.                     Button = { Color = "0 0 0 0", Command = undoCommand },
  874.                     RectTransform = { AnchorMin = "0.4 0.85", AnchorMax = "0.6 0.9" },
  875.                     Text = { Text = undoLabelText, FontSize = 12, Align = TextAnchor.MiddleCenter, Color = undoLabelColor, Font = "robotocondensed-bold.ttf" }
  876.                 }, panelName);
  877.             }
  878.  
  879.             float squareSize = 0.0875f;
  880.             for (int row = 0; row < 8; row++)
  881.             for (int col = 0; col < 8; col++)
  882.             {
  883.                 string squareName = $"Square_{row}_{col}";
  884.                 string squareColor = (row + col) % 2 == 0 ? config.Colors["LightSquare"] : config.Colors["DarkSquare"];
  885.                 if (!isSpectator && game.SelectedSquare == (row, col))
  886.                     squareColor = config.Colors["Selected"];
  887.                 else if (!isSpectator && game.LegalMoves.Contains((row, col)))
  888.                     squareColor = config.Colors["Highlight"];
  889.  
  890.                 var piece = game.Board[row, col];
  891.                 string pieceColor = piece.Type == PieceType.Empty ? "1 1 1 1" : (piece.IsWhite ? "0 0 1 1" : "1 0 0 1");
  892.  
  893.                 container.Add(new CuiButton
  894.                 {
  895.                     Button = { Color = squareColor, Command = isSpectator || game.GameEnded ? "" : $"chess click {game.GameId} {row} {col}" },
  896.                     RectTransform = { AnchorMin = $"{0.1 + col * squareSize} {0.1 + (7 - row) * squareSize}", AnchorMax = $"{0.1 + (col + 1) * squareSize} {0.1 + (8 - row) * squareSize}" },
  897.                     Text = { Text = GetPieceSymbol(piece), FontSize = 24, Align = TextAnchor.MiddleCenter, Color = pieceColor }
  898.                 }, panelName, squareName);
  899.             }
  900.  
  901.             container.Add(new CuiButton
  902.             {
  903.                 Button = { Color = config.Colors["ButtonClose"], Close = panelName },
  904.                 RectTransform = { AnchorMin = "0.7 0.05", AnchorMax = "0.9 0.1" },
  905.                 Text = { Text = "Close", FontSize = 12, Align = TextAnchor.MiddleCenter, Color = "1 1 1 1" }
  906.             }, panelName);
  907.  
  908.             if (!isSpectator && (string.IsNullOrEmpty(game.BluePlayerId) || string.IsNullOrEmpty(game.RedPlayerId)) && !game.GameEnded)
  909.             {
  910.                 container.Add(new CuiButton
  911.                 {
  912.                     Button = { Color = config.Colors["Button"], Command = $"chess join {game.GameId}" },
  913.                     RectTransform = { AnchorMin = "0.1 0.05", AnchorMax = "0.3 0.1" },
  914.                     Text = { Text = "Join Game", FontSize = 12, Align = TextAnchor.MiddleCenter, Color = "1 1 1 1" }
  915.                 }, panelName);
  916.             }
  917.  
  918.             CuiHelper.AddUi(basePlayer, container);
  919.             LogDebug($"Showing Chess GUI for {player.Name} (ID: {player.Id}) in game {game.GameId} ({(isSpectator ? "Spectator" : "Player")})");
  920.         }
  921.  
  922.         private void RegisterCommands()
  923.         {
  924.             AddCovalenceCommand("chess", nameof(ChessCommand));
  925.             AddCovalenceCommand("spectate", nameof(SpectateCommand));
  926.             AddCovalenceCommand("chesshelp", nameof(HelpCommand));
  927.         }
  928.  
  929.         private void ChessCommand(IPlayer player, string command, string[] args)
  930.         {
  931.             if (!player.HasPermission(PlayerPermission))
  932.             {
  933.                 player.Reply("You don't have permission to use this command!");
  934.                 return;
  935.             }
  936.  
  937.             if (args.Length == 0)
  938.             {
  939.                 player.Reply("Usage: /chess [join <gameid>|create [solo]|reset <gameid>|clearall|stats|requestundo <gameid>|acceptundo <gameid>|undo <gameid>]\nFor rules and info, use /chesshelp");
  940.                 return;
  941.             }
  942.  
  943.             switch (args[0].ToLower())
  944.             {
  945.                 case "join":
  946.                     if (args.Length != 2)
  947.                     {
  948.                         player.Reply("Usage: /chess join <gameid>");
  949.                         return;
  950.                     }
  951.                     JoinGame(player, args[1]);
  952.                     break;
  953.                 case "create":
  954.                     bool isSolo = args.Length > 1 && args[1].ToLower() == "solo";
  955.                     CreateGame(player, isSolo);
  956.                     break;
  957.                 case "reset":
  958.                     if (args.Length != 2)
  959.                     {
  960.                         player.Reply("Usage: /chess reset <gameid>");
  961.                         return;
  962.                     }
  963.                     if (player.HasPermission(AdminPermission))
  964.                         ResetGame(args[1], player);
  965.                     else
  966.                         player.Reply("You don't have permission to reset games!");
  967.                     break;
  968.                 case "clearall":
  969.                     if (player.HasPermission(AdminPermission))
  970.                         ClearAllGames(player);
  971.                     else
  972.                         player.Reply("You don't have permission to clear all games!");
  973.                     break;
  974.                 case "stats":
  975.                     ShowStats(player);
  976.                     break;
  977.                 case "requestundo":
  978.                     if (args.Length != 2)
  979.                     {
  980.                         player.Reply("Usage: /chess requestundo <gameid>");
  981.                         return;
  982.                     }
  983.                     if (!games.TryGetValue(args[1], out var gameRequestUndo))
  984.                     {
  985.                         player.Reply("Game not found!");
  986.                         return;
  987.                     }
  988.                     RequestUndo(player, gameRequestUndo);
  989.                     break;
  990.                 case "acceptundo":
  991.                     if (args.Length != 2)
  992.                     {
  993.                         player.Reply("Usage: /chess acceptundo <gameid>");
  994.                         return;
  995.                     }
  996.                     if (!games.TryGetValue(args[1], out var gameAcceptUndo))
  997.                     {
  998.                         player.Reply("Game not found!");
  999.                         return;
  1000.                     }
  1001.                     AcceptUndo(player, gameAcceptUndo);
  1002.                     break;
  1003.                 case "undo":
  1004.                     if (args.Length != 2)
  1005.                     {
  1006.                         player.Reply("Usage: /chess undo <gameid>");
  1007.                         return;
  1008.                     }
  1009.                     if (!games.TryGetValue(args[1], out var gameUndo))
  1010.                     {
  1011.                         player.Reply("Game not found!");
  1012.                         return;
  1013.                     }
  1014.                     PerformUndo(gameUndo, player);
  1015.                     break;
  1016.                 default:
  1017.                     player.Reply("Usage: /chess [join <gameid>|create [solo]|reset <gameid>|clearall|stats|requestundo <gameid>|acceptundo <gameid>|undo <gameid>]\nFor rules and info, use /chesshelp");
  1018.                     break;
  1019.             }
  1020.         }
  1021.  
  1022.         private void HelpCommand(IPlayer player, string command, string[] args)
  1023.         {
  1024.             var helpMessage = @"Welcome to Chess in Rust! Here's a simple guide for you, like explaining to a super smart third grader!
  1025.  
  1026. === How to Play Chess ===
  1027. Chess is a fun game on an 8x8 board with 6 types of pieces. Your goal is to trap the other player's king so it can't escape (called checkmate) or capture it. You take turns moving one piece at a time. Blue pieces move first!
  1028.  
  1029. **Pieces and How They Move**:
  1030. - **Pawn**: Like a brave little soldier! Moves forward 1 square (or 2 from its starting row). Captures diagonally 1 square. If it reaches the other side, it becomes a queen!
  1031. - **Rook**: Like a tank! Moves straight up, down, left, or right as far as it wants, unless blocked.
  1032. - **Knight**: Like a sneaky horse! Jumps in an L-shape: 2 squares one way, then 1 to the side. Can jump over other pieces!
  1033. - **Bishop**: Like a sharp shooter! Moves diagonally as far as it wants, unless blocked.
  1034. - **Queen**: The superstar! Moves any direction (straight or diagonal) as far as it wants, unless blocked.
  1035. - **King**: The big boss! Moves 1 square any direction. Protect it! If it's trapped (checkmate) or captured, you lose!
  1036.  
  1037. **Special Rules**:
  1038. - **Check**: If a piece can attack the king, it's in check. You *must* move to save the king (block, capture, or move the king).
  1039. - **Checkmate**: If the king can't escape check, the game ends, and the other player wins!
  1040. - **Draw**: If no one can win (like stalemate or not enough pieces), it's a tie.
  1041. - **Castling**: A special move where the king slides 2 squares toward a rook, and the rook jumps to the other side. Only if neither has moved and the path is clear!
  1042.  
  1043. **Winning**: Trap the enemy king in checkmate or capture it! You'll see a big victory message.
  1044.  
  1045. === Playing Chess in Rust ===
  1046. Here's how to play chess in this game:
  1047. - **Create a Game**: Type `/chess create` to start a new game. You'll get a game ID (like a secret code). Others can join it!
  1048. - **Solo Game**: Type `/chess create solo` to practice by yourself, playing both sides.
  1049. - **Join a Game**: Type `/chess join <gameid>` to jump into someone's game. You'll be Blue (first move) or Red.
  1050. - **Spectate**: Type `/spectate <gameid>` to watch a game without playing.
  1051. - **Check Stats**: Type `/chess stats` to see your wins, losses, draws, and the top 5 players.
  1052. - **Admin Stuff**: If you're a server boss, use `/chess reset <gameid>` to restart a game or `/chess clearall` to wipe all games.
  1053. - **Undo a Move**: Click the 'Request Undo' button (yellow) to ask the other player to undo the last move. If they accept, it turns green ('Undo') and clicking it undoes the move (once per turn). In solo mode, click twice to undo.
  1054.  
  1055. **How to Move**:
  1056. - Click a piece to select it (it turns yellow). Green squares show where it can go.
  1057. - Click a green square to move. For castling, select the king, then click the highlighted destination square. If it's wrong, it'll say Invalid move!
  1058. - Win and you'll earn 100 ECON and 160 RP (cool points for the server)!
  1059.  
  1060. Have fun playing chess! Type `/chess` to start, or ask an adult for help if you're stuck.";
  1061.  
  1062.             player.Reply(helpMessage);
  1063.         }
  1064.  
  1065.         private void AddSlidingMoves(ChessGame game, HashSet<(int Row, int Col)> moves, int row, int col, bool isWhite, (int, int)[] directions)
  1066.         {
  1067.             foreach (var (dr, dc) in directions)
  1068.             {
  1069.                 int r = row, c = col;
  1070.                 while (true)
  1071.                 {
  1072.                     r += dr;
  1073.                     c += dc;
  1074.                     if (r < 0 || r >= 8 || c < 0 || c >= 8) break;
  1075.                     if (game.Board[r, c].Type == PieceType.Empty)
  1076.                         moves.Add((r, c));
  1077.                     else
  1078.                     {
  1079.                         if (game.Board[r, c].IsWhite != isWhite)
  1080.                             moves.Add((r, c));
  1081.                         break;
  1082.                     }
  1083.                 }
  1084.             }
  1085.         }
  1086.  
  1087.         private bool IsCheckmate(ChessGame game)
  1088.         {
  1089.             if (!IsInCheck(game, game.WhiteToMove)) return false;
  1090.             for (int r = 0; r < 8; r++)
  1091.             for (int c = 0; c < 8; c++)
  1092.             {
  1093.                 var piece = game.Board[r, c];
  1094.                 if (piece.Type != PieceType.Empty && piece.IsWhite == game.WhiteToMove)
  1095.                 {
  1096.                     if (GetLegalMoves(game, r, c).Count > 0)
  1097.                         return false;
  1098.                 }
  1099.             }
  1100.             return true;
  1101.         }
  1102.  
  1103.         private bool IsStalemate(ChessGame game)
  1104.         {
  1105.             if (IsInCheck(game, game.WhiteToMove)) return false;
  1106.             for (int r = 0; r < 8; r++)
  1107.             for (int c = 0; c < 8; c++)
  1108.             {
  1109.                 var piece = game.Board[r, c];
  1110.                 if (piece.Type != PieceType.Empty && piece.IsWhite == game.WhiteToMove)
  1111.                 {
  1112.                     if (GetLegalMoves(game, r, c).Count > 0)
  1113.                         return false;
  1114.                 }
  1115.             }
  1116.             return true;
  1117.         }
  1118.  
  1119.         private bool IsInsufficientMaterial(ChessGame game)
  1120.         {
  1121.             int whiteKnights = 0, whiteBishops = 0, blackKnights = 0, blackBishops = 0;
  1122.             bool whiteHasOther = false, blackHasOther = false;
  1123.  
  1124.             for (int r = 0; r < 8; r++)
  1125.             for (int c = 0; c < 8; c++)
  1126.             {
  1127.                 var piece = game.Board[r, c];
  1128.                 if (piece.Type == PieceType.Empty) continue;
  1129.  
  1130.                 if (piece.IsWhite)
  1131.                 {
  1132.                     if (piece.Type == PieceType.Knight) whiteKnights++;
  1133.                     else if (piece.Type == PieceType.Bishop) whiteBishops++;
  1134.                     else if (piece.Type != PieceType.King) whiteHasOther = true;
  1135.                 }
  1136.                 else
  1137.                 {
  1138.                     if (piece.Type == PieceType.Knight) blackKnights++;
  1139.                     else if (piece.Type == PieceType.Bishop) blackBishops++;
  1140.                     else if (piece.Type != PieceType.King) blackHasOther = true;
  1141.                 }
  1142.             }
  1143.  
  1144.             if (!whiteHasOther && !blackHasOther && whiteKnights == 0 && whiteBishops == 0 && blackKnights == 0 && blackBishops == 0)
  1145.                 return true;
  1146.  
  1147.             if (!whiteHasOther && !blackHasOther &&
  1148.                 ((whiteKnights == 1 && whiteBishops == 0 && blackKnights == 0 && blackBishops == 0) ||
  1149.                  (whiteBishops == 1 && whiteKnights == 0 && blackKnights == 0 && blackBishops == 0) ||
  1150.                  (blackKnights == 1 && blackBishops == 0 && whiteKnights == 0 && whiteBishops == 0) ||
  1151.                  (blackBishops == 1 && blackKnights == 0 && whiteKnights == 0 && whiteBishops == 0)))
  1152.                 return true;
  1153.  
  1154.             return false;
  1155.         }
  1156.  
  1157.         private bool IsInCheck(ChessGame game, bool isWhite)
  1158.         {
  1159.             var kingPos = isWhite ? (game.KingPos.WhiteRow, game.KingPos.WhiteCol) : (game.KingPos.BlackRow, game.KingPos.BlackCol);
  1160.             for (int r = 0; r < 8; r++)
  1161.             for (int c = 0; c < 8; c++)
  1162.             {
  1163.                 var piece = game.Board[r, c];
  1164.                 if (piece.Type != PieceType.Empty && piece.IsWhite != isWhite)
  1165.                 {
  1166.                     var moves = GetLegalMoves(game, r, c);
  1167.                     foreach (var (moveRow, moveCol) in moves)
  1168.                     {
  1169.                         if (moveRow == kingPos.Item1 && moveCol == kingPos.Item2)
  1170.                         {
  1171.                             LogDebug($"King {(isWhite ? "White" : "Black")} in check by {piece.Type} at {r},{c}");
  1172.                             return true;
  1173.                         }
  1174.                     }
  1175.                 }
  1176.             }
  1177.             return false;
  1178.         }
  1179.  
  1180.         private void RecordGameResult(ChessGame game, string result)
  1181.         {
  1182.             if (game.IsSolo) return;
  1183.  
  1184.             if (!playerStats.ContainsKey(game.BluePlayerId))
  1185.                 playerStats[game.BluePlayerId] = new PlayerStats();
  1186.             if (!playerStats.ContainsKey(game.RedPlayerId))
  1187.                 playerStats[game.RedPlayerId] = new PlayerStats();
  1188.  
  1189.             if (result == "Blue")
  1190.             {
  1191.                 playerStats[game.BluePlayerId].Wins++;
  1192.                 playerStats[game.RedPlayerId].Losses++;
  1193.                 AwardRewards(game.BluePlayerId);
  1194.             }
  1195.             else if (result == "Red")
  1196.             {
  1197.                 playerStats[game.RedPlayerId].Wins++;
  1198.                 playerStats[game.BluePlayerId].Losses++;
  1199.                 AwardRewards(game.RedPlayerId);
  1200.             }
  1201.             else if (result == "Draw")
  1202.             {
  1203.                 playerStats[game.BluePlayerId].Draws++;
  1204.                 playerStats[game.RedPlayerId].Draws++;
  1205.             }
  1206.  
  1207.             SaveStats();
  1208.         }
  1209.  
  1210.         private void AwardRewards(string playerId)
  1211.         {
  1212.             var player = covalence.Players.FindPlayerById(playerId);
  1213.             if (player == null) return;
  1214.  
  1215.             if (Economics != null)
  1216.             {
  1217.                 Economics.Call("Deposit", playerId, 100.0);
  1218.                 player.Reply("You earned 100 ECON for your chess victory!");
  1219.             }
  1220.  
  1221.             if (ServerRewards != null)
  1222.             {
  1223.                 ServerRewards.Call("AddPoints", playerId, 160);
  1224.                 player.Reply("You earned 160 RP for your chess victory!");
  1225.             }
  1226.         }
  1227.  
  1228.         private string GetPieceSymbol(Piece piece)
  1229.         {
  1230.             if (piece.Type == PieceType.Empty) return "";
  1231.             var symbols = piece.IsWhite ?
  1232.                 new Dictionary<PieceType, string>
  1233.                 {
  1234.                     { PieceType.Pawn, "♙" },
  1235.                     { PieceType.Knight, "♘" },
  1236.                     { PieceType.Bishop, "♗" },
  1237.                     { PieceType.Rook, "♖" },
  1238.                     { PieceType.Queen, "♕" },
  1239.                     { PieceType.King, "♔" }
  1240.                 } :
  1241.                 new Dictionary<PieceType, string>
  1242.                 {
  1243.                     { PieceType.Pawn, "♟" },
  1244.                     { PieceType.Knight, "♞" },
  1245.                     { PieceType.Bishop, "♝" },
  1246.                     { PieceType.Rook, "♜" },
  1247.                     { PieceType.Queen, "♛" },
  1248.                     { PieceType.King, "♚" }
  1249.                 };
  1250.             return symbols[piece.Type];
  1251.         }
  1252.  
  1253.         private void DestroyGUI(BasePlayer player)
  1254.         {
  1255.             if (player == null) return;
  1256.             if (playerGUIs.TryGetValue(player.UserIDString, out string panelName))
  1257.             {
  1258.                 CuiHelper.DestroyUi(player, panelName);
  1259.                 playerGUIs.Remove(player.UserIDString);
  1260.             }
  1261.         }
  1262.  
  1263.         private void ShowBannerToAll(string message)
  1264.         {
  1265.             var container = new CuiElementContainer();
  1266.             string panelName = "ChessBanner";
  1267.  
  1268.             container.Add(new CuiPanel
  1269.             {
  1270.                 Image = { Color = config.Colors["Background"] },
  1271.                 RectTransform = { AnchorMin = "0 0.9", AnchorMax = "1 0.95" },
  1272.                 CursorEnabled = false
  1273.             }, "Overlay", panelName);
  1274.  
  1275.             container.Add(new CuiLabel
  1276.             {
  1277.                 Text = { Text = message, FontSize = 14, Align = TextAnchor.MiddleCenter, Color = "1 1 1 1" },
  1278.                 RectTransform = { AnchorMin = "0 0", AnchorMax = "1 1" }
  1279.             }, panelName);
  1280.  
  1281.             foreach (var player in BasePlayer.activePlayerList)
  1282.             {
  1283.                 if (player != null && player.IsConnected)
  1284.                 {
  1285.                     CuiHelper.DestroyUi(player, panelName);
  1286.                     CuiHelper.AddUi(player, container);
  1287.                 }
  1288.             }
  1289.  
  1290.             timer.Once(10f, () =>
  1291.             {
  1292.                 foreach (var player in BasePlayer.activePlayerList)
  1293.                 {
  1294.                     if (player != null && player.IsConnected)
  1295.                         CuiHelper.DestroyUi(player, panelName);
  1296.                 }
  1297.             });
  1298.         }
  1299.  
  1300.         private void SpectateCommand(IPlayer player, string command, string[] args)
  1301.         {
  1302.             if (!player.HasPermission(PlayerPermission))
  1303.             {
  1304.                 player.Reply("You don't have permission to use this command!");
  1305.                 return;
  1306.             }
  1307.  
  1308.             if (args.Length != 1)
  1309.             {
  1310.                 player.Reply("Usage: /spectate <gameid>");
  1311.                 return;
  1312.             }
  1313.  
  1314.             if (!games.TryGetValue(args[0], out var game))
  1315.             {
  1316.                 player.Reply("Game not found!");
  1317.                 return;
  1318.             }
  1319.  
  1320.             game.SpectatorIds.Add(player.Id);
  1321.             ShowGUI(player, game, true);
  1322.             player.Reply($"Now spectating game {args[0]}.");
  1323.             SaveData();
  1324.         }
  1325.  
  1326.         private void ShowStats(IPlayer player)
  1327.         {
  1328.             var stats = playerStats.GetValueOrDefault(player.Id, new PlayerStats());
  1329.             var message = $"Chess Stats for {player.Name}:\n" +
  1330.                           $"Wins: {stats.Wins}\n" +
  1331.                           $"Losses: {stats.Losses}\n" +
  1332.                           $"Draws: {stats.Draws}\n\n" +
  1333.                           "Leaderboard (Top 5):\n";
  1334.  
  1335.             var topPlayers = playerStats.OrderByDescending(x => x.Value.Wins).Take(5).ToList();
  1336.             int rank = 1;
  1337.             foreach (var entry in topPlayers)
  1338.             {
  1339.                 var p = covalence.Players.FindPlayerById(entry.Key);
  1340.                 message += $"{rank}. {p?.Name ?? "Unknown"} - {entry.Value.Wins} Wins\n";
  1341.                 rank++;
  1342.             }
  1343.  
  1344.             player.Reply(message);
  1345.         }
  1346.  
  1347.         private void CreateGame(IPlayer player, bool isSolo = false)
  1348.         {
  1349.             string gameId = Guid.NewGuid().ToString("N").Substring(0, 8);
  1350.             var game = new ChessGame(gameId) { IsSolo = isSolo };
  1351.             games[gameId] = game;
  1352.             if (isSolo)
  1353.             {
  1354.                 game.BluePlayerId = player.Id;
  1355.                 game.RedPlayerId = player.Id;
  1356.                 game.Status = "Solo Game in progress";
  1357.             }
  1358.             SaveData();
  1359.             player.Reply($"Created new chess game with ID: {gameId}. Join with /chess join {gameId}");
  1360.             ShowBannerToAll($"{player.Name} created a new chess game ({gameId})!");
  1361.         }
  1362.  
  1363.         private void JoinGame(IPlayer player, string gameId)
  1364.         {
  1365.             if (!games.TryGetValue(gameId, out var game))
  1366.             {
  1367.                 player.Reply("Game not found!");
  1368.                 return;
  1369.             }
  1370.  
  1371.             if (game.IsSolo)
  1372.             {
  1373.                 if (game.BluePlayerId == player.Id)
  1374.                 {
  1375.                     player.Reply("Opening your solo game!");
  1376.                     ShowGUI(player, game, false);
  1377.                     return;
  1378.                 }
  1379.                 else
  1380.                 {
  1381.                     player.Reply("This is a solo game! Create a new game with /chess create or /chess create solo.");
  1382.                     return;
  1383.                 }
  1384.             }
  1385.  
  1386.             if (game.BluePlayerId == player.Id || game.RedPlayerId == player.Id)
  1387.             {
  1388.                 player.Reply("You're already in this game!");
  1389.                 ShowGUI(player, game, false);
  1390.                 return;
  1391.             }
  1392.  
  1393.             if (string.IsNullOrEmpty(game.BluePlayerId))
  1394.             {
  1395.                 game.BluePlayerId = player.Id;
  1396.                 player.Reply("Joined as Blue!");
  1397.                 game.Status = string.IsNullOrEmpty(game.RedPlayerId) ? "Waiting for Red" : "Game in progress";
  1398.                 SaveData();
  1399.             }
  1400.             else if (string.IsNullOrEmpty(game.RedPlayerId))
  1401.             {
  1402.                 game.RedPlayerId = player.Id;
  1403.                 player.Reply("Joined as Red!");
  1404.                 game.Status = "Game in progress";
  1405.                 SaveData();
  1406.             }
  1407.             else
  1408.             {
  1409.                 player.Reply("Game is full! Spectate with /spectate <gameid> or create a new game with /chess create.");
  1410.                 return;
  1411.             }
  1412.  
  1413.             ShowGUI(player, game, false);
  1414.             ShowBannerToAll($"{player.Name} joined chess game {gameId}!");
  1415.         }
  1416.  
  1417.         private void ResetGame(string gameId, IPlayer player)
  1418.         {
  1419.             if (!games.ContainsKey(gameId))
  1420.             {
  1421.                 player.Reply("Game not found!");
  1422.                 return;
  1423.             }
  1424.  
  1425.             var game = games[gameId];
  1426.             foreach (var p in BasePlayer.activePlayerList)
  1427.             {
  1428.                 if (p.UserIDString == game.BluePlayerId || p.UserIDString == game.RedPlayerId || game.SpectatorIds.Contains(p.UserIDString))
  1429.                     DestroyGUI(p);
  1430.             }
  1431.             games.Remove(gameId);
  1432.             SaveData();
  1433.             player.Reply($"Chess game {gameId} reset!");
  1434.             ShowBannerToAll($"Chess game {gameId} has been reset!");
  1435.         }
  1436.  
  1437.         private void ClearAllGames(IPlayer player)
  1438.         {
  1439.             foreach (var game in games.Values)
  1440.             {
  1441.                 foreach (var p in BasePlayer.activePlayerList)
  1442.                 {
  1443.                     if (p.UserIDString == game.BluePlayerId || p.UserIDString == game.RedPlayerId || game.SpectatorIds.Contains(p.UserIDString))
  1444.                         DestroyGUI(p);
  1445.                 }
  1446.             }
  1447.             games.Clear();
  1448.             SaveData();
  1449.             player.Reply("All chess games have been cleared!");
  1450.             ShowBannerToAll("All chess games have been cleared!");
  1451.         }
  1452.  
  1453.         private string DumpBoardState(ChessGame game)
  1454.         {
  1455.             var sb = new StringBuilder();
  1456.             for (int row = 7; row >= 0; row--)
  1457.             {
  1458.                 for (int col = 0; col < 8; col++)
  1459.                 {
  1460.                     var piece = game.Board[row, col];
  1461.                     string symbol = piece.Type == PieceType.Empty ? "." : GetPieceSymbol(piece);
  1462.                     sb.Append(symbol + " ");
  1463.                 }
  1464.                 sb.AppendLine();
  1465.             }
  1466.             return sb.ToString();
  1467.         }
  1468.  
  1469.         private object OnServerCommand(ConsoleSystem.Arg arg)
  1470.         {
  1471.             var player = arg.Player();
  1472.             if (player == null) return null;
  1473.  
  1474.             var command = arg.cmd.Name;
  1475.             var args = arg.Args;
  1476.  
  1477.             if (command != "chess" || args == null || args.Length < 3 || args[0] != "click") return null;
  1478.  
  1479.             string playerId = player.UserIDString;
  1480.             float currentTime = Time.realtimeSinceStartup;
  1481.             if (lastClickTimes.TryGetValue(playerId, out float lastClickTime) && (currentTime - lastClickTime) < 0.75f)
  1482.             {
  1483.                 LogDebug($"Click debounced for player {playerId} in game {args[1]}");
  1484.                 return null;
  1485.             }
  1486.             lastClickTimes[playerId] = currentTime;
  1487.  
  1488.             LogDebug($"Received command: {command} with args: {string.Join(", ", args)}");
  1489.  
  1490.             string gameId = args[1];
  1491.             if (!games.TryGetValue(gameId, out var game))
  1492.             {
  1493.                 covalence.Players.FindPlayerById(playerId)?.Reply("Game not found!");
  1494.                 return null;
  1495.             }
  1496.  
  1497.             if (game.GameEnded)
  1498.             {
  1499.                 covalence.Players.FindPlayerById(playerId)?.Reply($"Game {gameId} has ended: {game.Status}. Start a new game with /chess create.");
  1500.                 return null;
  1501.             }
  1502.  
  1503.             if (!int.TryParse(args[2], out int row) || !int.TryParse(args[3], out int col) || row < 0 || row >= 8 || col < 0 || col >= 8)
  1504.             {
  1505.                 covalence.Players.FindPlayerById(playerId)?.Reply("Invalid square selection!");
  1506.                 return null;
  1507.             }
  1508.  
  1509.             var iPlayer = covalence.Players.FindPlayerById(playerId);
  1510.             if (iPlayer == null) return null;
  1511.  
  1512.             if (!game.IsSolo && game.BluePlayerId != iPlayer.Id && game.RedPlayerId != iPlayer.Id)
  1513.             {
  1514.                 iPlayer.Reply("You must join the game first! Use /chess join <gameid>");
  1515.                 return null;
  1516.             }
  1517.  
  1518.             if (!game.IsSolo && ((game.WhiteToMove && game.BluePlayerId != iPlayer.Id) || (!game.WhiteToMove && game.RedPlayerId != iPlayer.Id)))
  1519.             {
  1520.                 iPlayer.Reply("It's not your turn!");
  1521.                 return null;
  1522.             }
  1523.  
  1524.             LogDebug($"Processing click at {row},{col} in game {game.GameId}. SelectedSquare: {game.SelectedSquare}, LegalMoves: {string.Join(", ", game.LegalMoves)}");
  1525.  
  1526.             if (game.SelectedSquare == null)
  1527.             {
  1528.                 var piece = game.Board[row, col];
  1529.                 if (piece.Type != PieceType.Empty && piece.IsWhite == game.WhiteToMove)
  1530.                 {
  1531.                     LogDebug($"First click: Selecting piece {piece.Type} at {row},{col} in game {game.GameId}");
  1532.                     game.SelectedSquare = (row, col);
  1533.                     game.LegalMoves = GetLegalMoves(game, row, col);
  1534.                     game.IsCastlingSelected = piece.Type == PieceType.King && game.LegalMoves.Any(m => m.Row == row && (m.Col == 6 || m.Col == 2));
  1535.                     SaveData();
  1536.                     ShowGUI(iPlayer, game, false);
  1537.                     LogDebug($"Piece selected. LegalMoves updated: {string.Join(", ", game.LegalMoves)}, IsCastlingSelected: {game.IsCastlingSelected}");
  1538.                 }
  1539.                 else
  1540.                 {
  1541.                     iPlayer.Reply("Cannot select this piece!");
  1542.                     LogDebug($"Invalid selection attempt at {row},{col} in game {game.GameId}");
  1543.                 }
  1544.             }
  1545.             else
  1546.             {
  1547.                 var (fromRow, fromCol) = game.SelectedSquare.Value;
  1548.                 var selectedPiece = game.Board[fromRow, fromCol];
  1549.                 var targetPiece = game.Board[row, col];
  1550.  
  1551.                 if (row == fromRow && col == fromCol)
  1552.                 {
  1553.                     LogDebug($"Clicked same square {row},{col} in game {game.GameId}. Deselecting.");
  1554.                     game.SelectedSquare = null;
  1555.                     game.LegalMoves.Clear();
  1556.                     game.IsCastlingSelected = false;
  1557.                     SaveData();
  1558.                     ShowGUI(iPlayer, game, false);
  1559.                 }
  1560.                 else if (game.LegalMoves.Contains((row, col)))
  1561.                 {
  1562.                     LogDebug($"Second click: Attempting move from {fromRow},{fromCol} to {row},{col} in game {game.GameId}");
  1563.                     if (MakeMove(game, fromRow, fromCol, row, col))
  1564.                     {
  1565.                         game.SelectedSquare = null;
  1566.                         game.LegalMoves.Clear();
  1567.                         game.IsCastlingSelected = false;
  1568.                         SaveData();
  1569.                         foreach (var p in BasePlayer.activePlayerList)
  1570.                         {
  1571.                             if (p.UserIDString == game.BluePlayerId || p.UserIDString == game.RedPlayerId)
  1572.                                 ShowGUI(covalence.Players.FindPlayerById(p.UserIDString), game, false);
  1573.                             else if (game.SpectatorIds.Contains(p.UserIDString))
  1574.                                 ShowGUI(covalence.Players.FindPlayerById(p.UserIDString), game, true);
  1575.                         }
  1576.                     }
  1577.                     else
  1578.                     {
  1579.                         iPlayer.Reply("Invalid move!");
  1580.                         game.SelectedSquare = null;
  1581.                         game.LegalMoves.Clear();
  1582.                         game.IsCastlingSelected = false;
  1583.                         SaveData();
  1584.                         ShowGUI(iPlayer, game, false);
  1585.                         LogDebug($"Invalid move attempted from {fromRow},{fromCol} to {row},{col} in game {game.GameId}");
  1586.                     }
  1587.                 }
  1588.                 else
  1589.                 {
  1590.                     iPlayer.Reply("Invalid destination! Select a highlighted square or click the piece again to deselect.");
  1591.                     LogDebug($"Invalid destination {row},{col} clicked in game {game.GameId}. LegalMoves: {string.Join(", ", game.LegalMoves)}");
  1592.                 }
  1593.             }
  1594.  
  1595.             return null;
  1596.         }
  1597.     }
  1598. }
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement