From a8dfed5cdb17127f858c4f5ecd14953b2207f1e8 Mon Sep 17 00:00:00 2001 From: Michael Engelhardt Date: Sat, 7 Dec 2024 15:39:22 +0100 Subject: [PATCH] Add 4 Player capabilities --- .../bomberpengu/docs/re/dump/Connector.as | 58 +- .../bomberpengu/server/gamecode/Game.cs | 889 ++++++++++-------- 2 files changed, 556 insertions(+), 391 deletions(-) diff --git a/src/projects/bomberpengu/docs/re/dump/Connector.as b/src/projects/bomberpengu/docs/re/dump/Connector.as index eae4ddc..708f4b2 100644 --- a/src/projects/bomberpengu/docs/re/dump/Connector.as +++ b/src/projects/bomberpengu/docs/re/dump/Connector.as @@ -397,37 +397,50 @@ class Connector extends MovieClip break; case "startGame": trace("case: startGame"); - Game.playerNr = 2; - _loc8_ = _loc2_.attributes.name; - trace(_loc8_); - Connector.oppName = _loc8_; - Connector.updatePlayer2(Connector.oppName,"3"); + // Previously assumed only 2 players, now handle multiple + var _names = _loc2_.attributes.names; + var playersList = _names.split(","); + // Determine our player number + for (var i=0; i\n"); Connector.lastMsg = getTimer(); } - static function sendStartGame(targetPlayer) + // Updated to handle multiple players + static function sendStartGame(targetPlayers) { - trace("send startGame to " + targetPlayer); - Connector.oppName = targetPlayer; - Connector.updatePlayer2(Connector.oppName,"3"); + trace("send startGame to " + targetPlayers); + if (typeof(targetPlayers) == "string") { + targetPlayers = [targetPlayers]; + } + for (var i = 0; i < targetPlayers.length; i++) { + var tp = targetPlayers[i]; + Connector.updatePlayer2(tp,"3"); + } if(!Connector.gameStarted) { _root.chatBox.clearChatList(); @@ -539,7 +558,8 @@ class Connector extends MovieClip Connector.challengedAll = false; Connector.startPlayer = true; Game.playerNr = 1; - Connector.xmlSocket.send("\n"); + var namesStr = targetPlayers.join(","); + Connector.xmlSocket.send("\n"); Connector.lastMsg = getTimer(); _root.gotoAndStop("gameFrame"); } diff --git a/src/projects/bomberpengu/server/gamecode/Game.cs b/src/projects/bomberpengu/server/gamecode/Game.cs index 96ee3be..bc4c92a 100644 --- a/src/projects/bomberpengu/server/gamecode/Game.cs +++ b/src/projects/bomberpengu/server/gamecode/Game.cs @@ -6,459 +6,604 @@ namespace BomberPenguGame { - - public class Match - { - readonly Room room; - public List players = new List(); - - public Match(Room room, Player playerA, Player playerB) + public class Match { - this.room = room; - players.Add(playerA); - players.Add(playerB); - } + private readonly Room room; + public List players = new List(); + public const int MaxPlayers = 4; + private readonly object _lock = new object(); - public void Start() - { - foreach (var player in players) - { - player.Spawn(); - } - } - } - - public class Player : BasePlayer - { - public List challengedPlayers = new List(); - public Match match; - - public string Name; - public bool alive = false; - public bool challengeAll = false; - public bool freestyleRequested = false; - int wins = 0; - int loses = 0; - int draws = 0; - - public Player Enemy - { - get { - if (!InMatch) return null; - return match.players.FirstOrDefault(p => p != this); - } - } + public Match(Room room, IEnumerable participants) + { + this.room = room; + foreach (var player in participants) + { + AddPlayer(player); + } + } - public string Skill - { - get { return $"{wins}/{loses}/{draws}"; } - } + /// + /// Adds a player to the match if the maximum limit has not been reached. + /// + public void AddPlayer(Player player) + { + lock (_lock) + { + if (players.Count >= MaxPlayers) + throw new InvalidOperationException("Match is full"); - public bool InMatch - { - get { return match != null; } - } + players.Add(player); + player.match = this; + player.Spawn(); - public void Win() - { - wins++; - } + // Notify all players in the match about the new player + Broadcast("xml", $""); + } + } - public void Draw() - { - draws++; - } + /// + /// Starts the match by spawning all players. + /// + public void Start() + { + lock (_lock) + { + foreach (var player in players) + { + player.Spawn(); + } + } + } - public void Die() - { - if (!InMatch || !alive) return; - alive = false; - loses++; - } + /// + /// Checks the current game status to determine if there's a winner or a draw. + /// + public void CheckGameStatus() + { + lock (_lock) + { + var alivePlayers = players.Where(p => p.alive).ToList(); + if (alivePlayers.Count == 0) + { + // Draw + foreach (var p in players) + { + p.Draw(); + p.Send("xml", ""); + } + } + else if (alivePlayers.Count == 1) + { + // One winner + var winner = alivePlayers[0]; + winner.Win(); + foreach (var p in players) + { + p.Send("xml", $""); + } + } + // If more than one player is alive, the game continues + } + } - public void Spawn() - { - alive = true; - freestyleRequested = false; - } + /// + /// Determines if all players have requested freestyle. + /// + public bool AllFreestyleRequested() + { + lock (_lock) + { + return players.All(p => p.freestyleRequested); + } + } - public void SendChat(string sender, string message) - { - Send("xml", $"<{(InMatch ? "msgPlayer" : "msgAll")} name=\"{sender}\" msg=\"{message}\" />"); - } + /// + /// Initiates the freestyle round for all players. + /// + public void StartFreestyle() + { + lock (_lock) + { + foreach (var player in players) + { + player.Send("xml", ""); + var posList = "3:0,5:0,8:1,1:2,2:2,3:2,7:2,2:3,4:3,8:3,10:3,5:4,6:4,10:4,11:4,4:5,6:5,8:5,0:6,2:6,4:6,6:6,8:6,10:6,11:6,2:7,4:7,8:7,12:7,4:8,8:8,9:8,10:8,8:9,3:10,4:10,6:10,9:10".Split(','); + foreach (var item in posList) + { + var pos = item.Split(':'); + player.Send("xml", $""); + } + } + } + } - public void RequestFreestyle() - { - if (!alive || !InMatch || Enemy == null || freestyleRequested) return; - freestyleRequested = true; - - if (!Enemy.freestyleRequested) - { - SendChat("System", "You requested freestyle"); - Enemy.SendChat("System", "Your opponent requested freestyle. Type !fs to agree"); - } - else - { - SendChat("System", "Starting freestyle!"); - Enemy.SendChat("System", "Starting freestyle!"); - - foreach (var player in match.players) + /// + /// Broadcasts a message to all players in the match. + /// + public void Broadcast(string type, string message) { - player.Send("xml", ""); - var posList = "3:0,5:0,8:1,1:2,2:2,3:2,7:2,2:3,4:3,8:3,10:3,5:4,6:4,10:4,11:4,4:5,6:5,8:5,0:6,2:6,4:6,6:6,8:6,10:6,11:6,2:7,4:7,8:7,12:7,4:8,8:8,9:8,10:8,8:9,3:10,4:10,6:10,9:10".Split(','); - foreach (var item in posList) - { - var pos = item.Split(':'); - player.Send("xml", $""); - } + lock (_lock) + { + foreach (var player in players) + { + player.Send(type, message); + } + } } - } } - public void ShowHelp() + public class Player : BasePlayer { - var lines = new Dictionary() - { - { "!fs|!freestyle", "Vote for a freestyle round" }, - { "!bug", "Report a bug" }, - { "!h|!help", "Show this help" }, - }; - - - foreach (var entry in lines) - { - SendChat(entry.Key, entry.Value); - } - } + public List challengedPlayers = new List(); + public Match match; + + public string Name; + public bool alive = false; + public bool challengeAll = false; + public bool freestyleRequested = false; + int wins = 0; + int loses = 0; + int draws = 0; + + public string Skill + { + get { return $"{wins}/{loses}/{draws}"; } + } - public string GetState(Player askingPlayer) - { - if (askingPlayer == this) return "5"; + public bool InMatch + { + get { return match != null; } + } - var state = InMatch ? "3" : ""; + /// + /// Increments the win count for the player. + /// + public void Win() + { + wins++; + } - if (challengeAll) state = "1" + state; - else if (askingPlayer.challengeAll) state = "2" + state; - else if (state == "") state = "0"; + /// + /// Increments the draw count for the player. + /// + public void Draw() + { + draws++; + } - return state; - } + /// + /// Handles the player's death within a match. + /// + public void Die() + { + if (!InMatch || !alive) return; + alive = false; + loses++; + match.CheckGameStatus(); + } - public void Disconnect(string reason) - { - Send("xml", $"{reason}"); - Disconnect(); - } - } + /// + /// Spawns the player, resetting relevant states. + /// + public void Spawn() + { + alive = true; + freestyleRequested = false; + } - [RoomType("Lobby")] - public class Room : Game - { + /// + /// Sends a chat message to either the match or all players. + /// + public void SendChat(string sender, string message) + { + Send("xml", $"<{(InMatch ? "msgPlayer" : "msgAll")} name=\"{sender}\" msg=\"{message}\" />"); + } - public override bool AllowUserJoin(Player player) - { - var hasName = player.JoinData.TryGetValue("name", out string name); - if (!hasName || name == "") - { - player.Disconnect("Missing name!"); - return false; - } - - if (Players.Count(p => p.Name == name) > 0) - { - player.Disconnect("Name taken!"); - return false; - } - - player.Name = name; - - return true; - } + /// + /// Handles the player's request to start a freestyle round. + /// + public void RequestFreestyle() + { + if (!alive || !InMatch || freestyleRequested) return; + freestyleRequested = true; - public override void UserJoined(Player player) - { - var userListXml = ""; + if (!match.AllFreestyleRequested()) + { + SendChat("System", "You requested freestyle"); + // Notify other players that a freestyle request has been made + foreach (var p in match.players.Where(p => p != this)) + { + p.SendChat("System", $"Player {this.Name} has requested freestyle. Type !fs to agree"); + } + } + else + { + // All players have requested freestyle, start it + foreach (var p in match.players) + { + p.SendChat("System", "Starting freestyle!"); + } + match.StartFreestyle(); + } + } - foreach (var p in Players) - { - // If its another player, inform him about us - if (p != player) + /// + /// Sends help information to the player. + /// + public void ShowHelp() { - p.Send("xml", $""); + var lines = new Dictionary() + { + { "!fs|!freestyle", "Vote for a freestyle round" }, + { "!bug", "Report a bug" }, + { "!h|!help", "Show this help" }, + }; + + foreach (var entry in lines) + { + SendChat(entry.Key, entry.Value); + } } - userListXml += $""; - } + /// + /// Retrieves the state of the player from the perspective of another player. + /// + public string GetState(Player askingPlayer) + { + if (askingPlayer == this) return "5"; - userListXml += ""; - player.Send("xml", userListXml); - } + var inMatchState = InMatch ? "3" : ""; + string state = ""; - public override void UserLeft(Player player) - { - foreach (var p in Players) - { - if (p == player) continue; + if (challengeAll) state = "1" + inMatchState; + else if (askingPlayer.challengeAll) state = "2" + inMatchState; + else if (inMatchState == "") state = "0"; + else state = inMatchState; - p.challengedPlayers.Remove(player); - } + return state; + } - Broadcast("xml", $""); + /// + /// Disconnects the player with a specific reason. + /// + public void Disconnect(string reason) + { + Send("xml", $"{reason}"); + Disconnect(); + } } - private void OnXml(Player player, XmlDocument xml, string xmlString) + [RoomType("Lobby")] + public class Room : Game { - string rootName = xml.DocumentElement.Name; - if (rootName == "auth") - { - var name = xml.DocumentElement.Attributes["name"]?.Value ?? ""; - if (name != player.Name) + public override bool AllowUserJoin(Player player) { - player.Disconnect("Name mismatch!"); - return; + var hasName = player.JoinData.TryGetValue("name", out string name); + if (!hasName || name == "") + { + player.Disconnect("Missing name!"); + return false; + } + + if (Players.Count(p => p.Name == name) > 0) + { + player.Disconnect("Name taken!"); + return false; + } + + player.Name = name; + + return true; } - return; - } + public override void UserJoined(Player player) + { + var userListXml = ""; + + foreach (var p in Players) + { + // If it's another player, inform them about the new player + if (p != player) + { + p.Send("xml", $""); + } - if (rootName == "toRoom") - { - if (!player.InMatch) return; + userListXml += $""; + } - player.match.players.Remove(player); - player.match = null; + userListXml += ""; + player.Send("xml", userListXml); + } - foreach (var p in Players) + public override void UserLeft(Player player) { - if (p == player) continue; - p.Send("xml", $""); + foreach (var p in Players) + { + if (p == player) continue; + p.challengedPlayers.Remove(player); + } + + if (player.InMatch) + { + player.Die(); // This will trigger game status check + } + + Broadcast("xml", $""); } - return; - } + private void OnXml(Player player, XmlDocument xml, string xmlString) + { + string rootName = xml.DocumentElement.Name; - if (rootName == "challenge") - { - var targetPlayerName = xml.DocumentElement.Attributes["name"]?.Value ?? ""; - if (targetPlayerName == "") return; + if (rootName == "auth") + { + var name = xml.DocumentElement.Attributes["name"]?.Value ?? ""; + if (name != player.Name) + { + player.Disconnect("Name mismatch!"); + return; + } + + return; + } - var targetPlayer = Players.Where(p => p.Name == targetPlayerName).FirstOrDefault(); - if (targetPlayer == null) return; + if (rootName == "toRoom") + { + if (!player.InMatch) return; - player.challengedPlayers.Add(targetPlayer); + lock (player.match) + { + player.match.players.Remove(player); + player.match = null; - targetPlayer.Send("xml", $"\0"); - return; - } + foreach (var p in Players) + { + if (p == player) continue; + p.Send("xml", $""); + } + } - if (rootName == "remChallenge") - { - var targetPlayerName = xml.DocumentElement.Attributes["name"]?.Value ?? ""; - if (targetPlayerName == "") return; + return; + } - var targetPlayer = player.challengedPlayers.Where(p => p.Name == targetPlayerName).FirstOrDefault(); - if (targetPlayer == null) return; + if (rootName == "challenge") + { + var targetPlayerName = xml.DocumentElement.Attributes["name"]?.Value ?? ""; + if (targetPlayerName == "") return; - player.challengedPlayers.Remove(targetPlayer); + var targetPlayer = Players.FirstOrDefault(p => p.Name == targetPlayerName); + if (targetPlayer == null) return; - targetPlayer.Send("xml", $"\0"); - return; - } + if (!player.challengedPlayers.Contains(targetPlayer)) + { + player.challengedPlayers.Add(targetPlayer); + } - if (rootName == "challengeAll") - { - if (player.challengeAll) return; + targetPlayer.Send("xml", $"\0"); + return; + } - player.challengeAll = true; + if (rootName == "remChallenge") + { + var targetPlayerName = xml.DocumentElement.Attributes["name"]?.Value ?? ""; + if (targetPlayerName == "") return; - foreach (var p in Players) - { - if (p == player) continue; - if (player.challengedPlayers.Contains(p)) continue; + var targetPlayer = player.challengedPlayers.FirstOrDefault(p => p.Name == targetPlayerName); + if (targetPlayer == null) return; - player.challengedPlayers.Add(p); - p.Send("xml", $"\0"); - } + player.challengedPlayers.Remove(targetPlayer); - return; - } + targetPlayer.Send("xml", $"\0"); + return; + } - if (rootName == "remChallengeAll") - { - if (!player.challengeAll) return; - player.challengeAll = false; + if (rootName == "challengeAll") + { + if (player.challengeAll) return; - foreach (var p in player.challengedPlayers) - { - p.Send("xml", $"\0"); - } + player.challengeAll = true; - player.challengedPlayers.Clear(); + foreach (var p in Players) + { + if (p == player) continue; + if (!player.challengedPlayers.Contains(p)) + { + player.challengedPlayers.Add(p); + p.Send("xml", $"\0"); + } + } - return; - } + return; + } - if (rootName == "startGame") - { - var targetPlayerName = xml.DocumentElement.Attributes["name"]?.Value ?? ""; - if (targetPlayerName == "") return; + if (rootName == "remChallengeAll") + { + if (!player.challengeAll) return; + player.challengeAll = false; - var targetPlayer = Players.Where(p => p.Name == targetPlayerName).FirstOrDefault(); - if (targetPlayer == null) return; - if (player.match != targetPlayer.match) return; + foreach (var p in player.challengedPlayers) + { + p.Send("xml", $"\0"); + } - // If there is no match yet, create it - if (player.match == null) - { - player.match = new Match(this, player, targetPlayer); - targetPlayer.match = player.match; - } + player.challengedPlayers.Clear(); - player.challengeAll = false; - targetPlayer.challengeAll = false; + return; + } - targetPlayer.Send("xml", $""); + if (rootName == "startGame") + { + // Expecting a comma-separated list of player names + var targetPlayerNames = xml.DocumentElement.Attributes["names"]?.Value ?? ""; + if (string.IsNullOrEmpty(targetPlayerNames)) return; + + var targetNames = targetPlayerNames.Split(',').Select(name => name.Trim()).ToList(); + var targetPlayers = Players.Where(p => targetNames.Contains(p.Name)).ToList(); + + if (targetPlayers.Count == 0) + return; + + // Ensure all target players are available and not already in a match + foreach (var targetPlayer in targetPlayers) + { + if (targetPlayer.InMatch) + return; // Optionally, notify the initiating player that a target is unavailable + } + + // Create the match with the initiating player and target players + var participants = new List { player }; + participants.AddRange(targetPlayers); + + if (participants.Count > Match.MaxPlayers) + { + player.Send("xml", "Cannot start a match with more than 4 players."); + return; + } + + var match = new Match(this, participants); + match.Start(); + + // Notify all participants + foreach (var p in match.players) + { + p.Send("xml", $""); + } + + return; + } - player.match.Start(); + if (rootName == "winGame") + { + if (!player.InMatch) return; + player.Win(); + return; + } - return; - } + if (rootName == "drawGame") + { + if (!player.InMatch) return; + player.Draw(); + return; + } - if (rootName == "winGame") - { - if (!player.InMatch) return; - player.Win(); - return; - } + if (rootName == "die") + { + if (!player.InMatch) return; + if (!player.alive) return; + player.Die(); + return; + } - if (rootName == "drawGame") - { - if (!player.InMatch) return; - player.Draw(); - return; - } + if (rootName == "msgPlayer" || rootName == "msgAll") + { + var msg = xml.DocumentElement.Attributes["msg"]?.Value ?? ""; + if (msg == "") return; + + if (msg.StartsWith("!")) + { + Console.WriteLine($"Command: '{msg}'"); + if (msg == "!h" || msg == "!help") + { + player.ShowHelp(); + } + else if (msg == "!fs" || msg == "!freestyle") + { + player.RequestFreestyle(); + } + else if (msg == "!bug") + { + player.SendChat("System", "Report bugs at: https://github.com/freehuntx/flashback/issues"); + } + } + else + { + if (player.InMatch) + { + // Send to all other players in the match + foreach (var p in player.match.players.Where(p => p != player)) + { + p.SendChat(player.Name, msg); + } + } + else + { + foreach (var p in Players) + { + if (p == player || p.InMatch) continue; + p.SendChat(player.Name, msg); + } + } + } + + return; + } - if (rootName == "die") - { - if (!player.InMatch) return; - if (!player.alive) return; - player.Die(); + if (rootName == "surrender") + { + if (!player.InMatch) return; - if (player.Enemy != null && !player.Enemy.alive) - { - player.Draw(); - player.Enemy.Draw(); + player.Die(); + // After a surrender, CheckGameStatus() is already called in Die(). + // If there's exactly one winner, we announce it. If no one alive, it's a draw. + // If multiple alive, no immediate winner is decided. - player.Send("xml", $""); - player.Enemy.Send("xml", $""); - } - } + return; + } - if (rootName == "msgPlayer" || rootName == "msgAll") - { + // Broadcast these tags to all other match players + if ( + rootName == "playAgain" || + rootName == "Tag10" || + rootName == "Tag11" || + rootName == "Tag12" || + rootName == "Tag14" || + rootName == "Tag16" || + rootName == "Tag17" || + rootName == "Tag18" || + rootName == "Tag19" + ) + { + if (!player.InMatch) return; + + foreach (var p in player.match.players) + { + if (p == player) continue; + p.Send("xml", xmlString); + } + return; + } - var msg = xml.DocumentElement.Attributes["msg"]?.Value ?? ""; - if (msg == "") return; + if (rootName == "ping") + { + player.Send("xml", ""); + return; + } - if (msg.StartsWith("!")) - { - Console.WriteLine($"Command: '{msg}'"); - if (msg == "!h" || msg == "!help") - { - player.ShowHelp(); - } - else if (msg == "!fs" || msg == "!freestyle") - { - player.RequestFreestyle(); - } - else if (msg == "!bug") - { - player.SendChat("System", "Report bugs at: https://github.com/freehuntx/flashback/issues"); - } - /*else if (msg.StartsWith("!d ")) - { - player.Send("xml", msg.Replace("(", "<").Replace(")", ">").Substring(3)); - }*/ + Console.WriteLine($"Unhandled: {xmlString}"); } - else + + // This method is called when a player sends a message into the server code + public override void GotMessage(Player player, Message message) { - if (player.InMatch) - { - player.Enemy?.SendChat(player.Name, msg); - } - else - { - foreach (var p in Players) + if (message.Type == "xml") { - if (p == player || p.InMatch) continue; - p.SendChat(player.Name, msg); + string xmlString = message.GetString(0); + XmlDocument xmlDoc = new XmlDocument(); + try + { + xmlDoc.LoadXml(xmlString); + } + catch (Exception ex) + { + Console.WriteLine($"Invalid XML from player {player.Name}: {ex.Message}"); + return; + } + OnXml(player, xmlDoc, xmlString); + return; } - } - } - return; - } - - if (rootName == "surrender") - { - if (!player.InMatch) return; - var enemy = player.match.players.FirstOrDefault(p => p != player); - - player.Die(); - - player.Send("xml", $""); - enemy?.Send("xml", $""); - - return; - } - - // These should be sent to the enemy - if ( - rootName == "die" || - rootName == "playAgain" || - rootName == "Tag10" || - rootName == "Tag11" || - rootName == "Tag12" || - rootName == "Tag14" || - rootName == "Tag16" || - rootName == "Tag17" || - rootName == "Tag18" || - rootName == "Tag19" - ) - { - if (!player.InMatch) return; - - foreach (var p in player.match.players) - { - if (p == player) continue; - p.Send("xml", xmlString); + Console.WriteLine("[Error] Unhandled message type: " + message.Type); } - return; - } - - if (rootName == "ping") - { - player.Send("xml", ""); - return; - } - - Console.WriteLine($"Unhandled: {xmlString}"); - } - - // This method is called when a player sends a message into the server code - public override void GotMessage(Player player, Message message) - { - if (message.Type == "xml") - { - string xmlString = message.GetString(0); - XmlDocument xmlDoc = new XmlDocument(); - xmlDoc.LoadXml(xmlString); - OnXml(player, xmlDoc, xmlString); - return; - } - - Console.WriteLine("[Error] Unhandled message type: " + message.Type); } - } }