using System; using System.Data; using System.Text; using System.Threading; using System.Collections.ObjectModel; using Holo.Virtual.Users; using Holo.Virtual.Rooms.Pathfinding; using Ion.Storage; namespace Holo.Virtual.Rooms.Games { internal class Game { #region Declares /// /// The gameLobby object where this game is hosted in. /// internal gameLobby Lobby; /// /// Indicates if this game is of the type 'BattleBall'. If false, then the game is of the type 'SnowStorm'. /// internal bool isBattleBall; /// /// The ID of this game. /// internal int ID; /// /// The display name of this game. /// internal string Name; /// /// The ID of the map that is played with this game. /// internal int mapID; /// /// The total amount of gametime in seconds. /// internal int totalTime; /// /// The amount of gametime is seconds that is still left on this game. /// internal int leftTime; internal gameState State; /// /// BattleBall only. The numbers of the enabled powerups for this game. /// internal int[] enabledPowerups; /// /// The gamePlayer object (bbPlayer or ssPlayer) of the virtual user that has created this game. /// internal gamePlayer Owner; /// /// Collection with the gamePlayer objects of the virtual users that have 'checked in' to this game sub and should receive updates about team status etc. /// internal Collection Subviewers = new Collection(); internal Collection[] Teams; #region Map declares #region Enumerators etc internal enum gameState { Waiting = 0, Started = 1, Ended = 2 }; /// /// Represents the state of a tile on the gamemap in a game of 'BattleBall'. /// private enum bbTileState { Default = 0, Touched = 1, Clicked = 2, Pressed = 3, Sealed = 4 } /// /// Represents the color of the team that owns a tile on the gamemap in a game of 'BattleBall'. /// private enum bbTileColor { Disabled = -2, Default = -1, Red = 0, Blue = 1, Yellow = 2, Green = 3 } #endregion /// /// The amount of seconds that the 'loading game' bar still has to be displayed. /// private int leftCountdownSeconds = Config.Game_Countdown_Seconds; /// /// The length of the arena. private int bX; /// /// The max width of the arena. /// private int bY; /// /// The heightmap of the arena. /// internal string Heightmap; private byte[,] Height; private bool[,] Blocked; private bbTileState[,] bbTState; private bbTileColor[,] bbTColor; private Thread updateHandler; #endregion #endregion #region Constructors/destructor /// /// Initializes a 'BattleBall' game. /// /// The gameLobby object of the lobby where this game is hosted in. /// The ID of this game. /// The display name of this game. /// The ID of the map that is played with this game. /// The amount of teams that is used with the game. /// The numbers of the enabled powerups for this game. /// The gamePlayer instance of the virtual user that has created this game. internal Game(gameLobby Lobby, int ID, string Name, int mapID, int teamAmount, int[] enabledPowerups, gamePlayer Owner) { this.Lobby = Lobby; this.ID = ID; this.Name = Name; this.mapID = mapID; this.Owner = Owner; this.enabledPowerups = enabledPowerups; this.totalTime = Config.Game_BattleBall_gameLength_Seconds; this.leftTime = Config.Game_BattleBall_gameLength_Seconds; this.isBattleBall = true; // Dimension teams this.Teams = new Collection[teamAmount]; for (int i = 0; i < teamAmount; i++) Teams[i] = new Collection(); } /// /// Initializes a 'SnowStorm' game. /// /// The gameLobby object of the lobby where this game is hosted in. /// The ID of this game. /// The display name of this game. /// The ID of the map that is played with this game. /// The amount of teams that is used with the game. /// The total amount of gametime in seconds. /// The gamePlayer instance of the virtual user that has created this game. internal Game(gameLobby Lobby, int ID, string Name, int mapID, int teamAmount, int totalTime, gamePlayer Owner) { this.Lobby = Lobby; this.ID = ID; this.Name = Name; this.mapID = mapID; this.Owner = Owner; this.totalTime = totalTime; this.leftTime = totalTime; this.isBattleBall = false; // Dimension teams this.Teams = new Collection[teamAmount]; for (int i = 0; i < teamAmount; i++) Teams[i] = new Collection(); } /// /// Stops the game, and removes all players and subviewers from the game. /// internal void Abort() { try { updateHandler.Abort(); } catch { } string Data = "Cm" + "H"; for (int i = 0; i < Teams.Length; i++) foreach (gamePlayer Member in Teams[i]) { if (Member.User != null) { if (State == gameState.Waiting) Member.User.sendData(Data); Member.User.gamePlayer = null; } } foreach (gamePlayer subViewer in Subviewers) { if (subViewer.User != null) { if (State == gameState.Waiting) subViewer.User.sendData(Data); subViewer.User.gamePlayer = null; } } } #endregion #region Player management /// /// Moves a player to a certain team, or removes a player from the game. /// /// The gamePlayer object of the player to move. /// The ID of the team where the player was in before. If the player is new to the game, then this parameter should be -1. /// The ID of the team where the player is moving to. If the player is leaving the game, then this parameter should be -1. internal void movePlayer(gamePlayer Player, int fromTeamID, int toTeamID) { try { if (fromTeamID != -1) // Remove player from previous team Teams[fromTeamID].Remove(Player); if (toTeamID != -1) // User added to team { if (Teams[toTeamID].Contains(Player) == false) Teams[toTeamID].Add(Player); Player.teamID = toTeamID; } else { Teams[Player.teamID].Remove(Player); Player.User.gamePlayer = null; } sendData("Ci" + this.Sub); } catch { } } /// /// Indicates if the game can be launched. Games are 'launchable' when there are atleast two teams with players. /// internal bool Launchable { get { int activeTeamCount = 0; for (int i = 0; i < Teams.Length; i++) if (Teams[i].Count != 0) // This team has got players activeTeamCount++; return (activeTeamCount > 1); // Atleast two teams with players } } /// /// Returns a boolean that indicates if a certain team has space left for new team members. On error, false is returned. /// /// The ID of the team to check. internal bool teamHasSpace(int teamID) { try { int m = 0; int l = Teams.Length; if (l == 2) m = 6; else if (l == 3) m = 4; else if (l == 4) m = 3; return (Teams[teamID].Count < m); } catch { return false; } } /// /// Sends a single packet to all players, spectators and subviewers of this game. /// /// The packet to send. internal void sendData(string Data) { for (int i = 0; i < Teams.Length; i++) foreach (gamePlayer Member in Teams[i]) Member.sendData(Data); if (State == gameState.Waiting) // Game hasn't started yet { foreach (gamePlayer subViewer in Subviewers) subViewer.sendData(Data); } } #endregion #region Properties internal string Sub { get { StringBuilder Entry = new StringBuilder(Encoding.encodeVL64((int)State)); if (Launchable) // Atleast two teams with waiting players Entry.Append("I"); // 1 = Game can be started else Entry.Append("H"); // 0 = Game can't be started yet Entry.Append(Name + Convert.ToChar(2) + Encoding.encodeVL64(Owner.roomUID) + Owner.User._Username + Convert.ToChar(2)); if (isBattleBall == false) // SnowStorm game Entry.Append(Encoding.encodeVL64(totalTime)); Entry.Append(Encoding.encodeVL64(mapID) + Encoding.encodeVL64(0) + Encoding.encodeVL64(Teams.Length)); for (int i = 0; i < Teams.Length; i++) { Entry.Append(Encoding.encodeVL64(Teams[i].Count)); foreach (gamePlayer Member in Teams[i]) Entry.Append(Encoding.encodeVL64(Member.roomUID) + Member.User._Username + Convert.ToChar(2)); } if (isBattleBall) // BattleBall game { for (int i = 0; i < enabledPowerups.Length; i++) Entry.Append(enabledPowerups[i] + ","); Entry.Append("9" + Convert.ToChar(2)); } else // SnowStorm game Entry.Append(Encoding.encodeVL64(leftTime) + Encoding.encodeVL64(mapID)); return Entry.ToString(); } } #endregion #region Arena internal void startGame() { State = gameState.Started; foreach (gamePlayer subViewer in Subviewers) subViewer.sendData(this.Sub); using (DatabaseClient dbClient = Eucalypt.dbManager.GetClient()) { this.Heightmap = dbClient.getString("SELECT heightmap FROM games_maps WHERE type = '" + Lobby.Type + "' AND id = '" + mapID + "'"); } string[] _H = Heightmap.Split(Convert.ToChar(13)); this.bX = _H[0].Length; this.bY = _H.Length - 1; string[] _T = null; if (isBattleBall) { this.bbTState = new bbTileState[bX, bY]; this.bbTColor = new bbTileColor[bX, bY]; using (DatabaseClient dbClient = Eucalypt.dbManager.GetClient()) { _T = dbClient.getString("SELECT bb_tilemap FROM games_maps WHERE type = 'bb' AND id = '" + mapID + "'").Split(Convert.ToChar(13)); } } this.Height = new byte[bX, bY]; this.Blocked = new bool[bX, bY]; for (int y = 0; y < bY; y++) { for (int x = 0; x < bX; x++) { string q = _H[y].Substring(x, 1); if (q == "x") { Blocked[x, y] = true; if (isBattleBall) { bbTColor[x, y] = bbTileColor.Disabled; bbTState[x, y] = bbTileState.Sealed; } } else { Height[x, y] = Convert.ToByte(q); if (isBattleBall) { if (_T[y].Substring(x, 1) == "1") bbTColor[x, y] = bbTileColor.Default; else { bbTColor[x, y] = bbTileColor.Disabled; bbTState[x, y] = bbTileState.Sealed; } } } } } int j = 0; for (int i = 0; i < Teams.Length; i++) { if (Teams[i].Count == 0) // Empty team continue; DataRow dRow; using (DatabaseClient dbClient = Eucalypt.dbManager.GetClient()) { dRow = dbClient.getRow("SELECT x,y,z FROM games_maps_playerspawns WHERE type = '" + Lobby.Type + "' AND mapid = '" + mapID + "' AND teamid = '" + i + "'"); } int spawnX = Convert.ToInt32(dRow["x"]); int spawnY = Convert.ToInt32(dRow["y"]); byte spawnZ = Convert.ToByte(dRow["z"]); bool Flip = false; foreach (gamePlayer Player in Teams[i]) { Player.roomUID = j; rs: try { while (Blocked[spawnX, spawnY]) { if (spawnZ == 0 || spawnZ == 2) { if (Flip) spawnX -= 1; else spawnX += 1; } else if (spawnZ == 4 || spawnZ == 6) { if (Flip) spawnY -= 1; else spawnY += 1; } } Flip = (Flip != true); } catch { Flip = (Flip != true); goto rs; } // Out of range of map, reverse spawn Blocked[spawnX, spawnY] = true; Player.X = spawnX; Player.Y = spawnY; Player.Z = spawnZ; Player.H = Height[spawnX, spawnY]; Player.goalX = -1; Player.enteringGame = true; j++; Out.WriteLine("Assigned spawnpoint [" + spawnX + "," + spawnY + "] to player [" + j + ", team id: " + Player.teamID + "]"); } } sendData("Cq" + Encoding.encodeVL64(-1)); // Send players to arena new Eucalypt.commonDelegate(countDownTicker).BeginInvoke(null, null); // Start countdown bar } /// /// Counts /// private void countDownTicker() { while (leftCountdownSeconds > 0) { leftCountdownSeconds--; Thread.Sleep(1000); } using (DatabaseClient dbClient = Eucalypt.dbManager.GetClient()) { for (int i = 0; i < Teams.Length; i++) foreach (gamePlayer Player in Teams[i]) { Player.User._Tickets -= 2; Player.User.sendData("A|" + Player.User._Tickets); dbClient.runQuery("UPDATE users SET tickets = '" + Player.User._Tickets + "' WHERE id = '" + Player.User.userID + "' LIMIT 1"); } } // Start game updateHandler = new Thread(new ThreadStart(updateLoop)); updateHandler.Start(); sendData("Cw" + Encoding.encodeVL64(totalTime)); } internal string getMap() { StringBuilder Setup = new StringBuilder(); if (isBattleBall) { Setup.Append("I" + Encoding.encodeVL64(leftCountdownSeconds) + Encoding.encodeVL64(Config.Game_Countdown_Seconds) + "H" + Encoding.encodeVL64(bX) + Encoding.encodeVL64(bY)); for (int y = 0; y < bY; y++) for (int x = 0; x < bX; x++) Setup.Append(Encoding.encodeVL64((int)bbTColor[x, y]) + Encoding.encodeVL64((int)bbTState[x, y])); Setup.Append("IH"); } else Setup.Append("holo.cast.not_finished"); return Setup.ToString(); } internal string getPlayers() { StringBuilder Setup = null; StringBuilder Helper = new StringBuilder(); Random RND = new Random(DateTime.Now.Millisecond * totalTime); if (isBattleBall) { int p = 0; for (int i = 0; i < Teams.Length; i++) foreach (gamePlayer Player in Teams[i]) { Out.WriteLine(Player.roomUID + "!"); Helper.Append("H" + Encoding.encodeVL64(p) + Encoding.encodeVL64(Player.X) + Encoding.encodeVL64(Player.Y) + Encoding.encodeVL64(Player.H) + Encoding.encodeVL64(Player.Z) + "HM" + Player.User._Username + Convert.ToChar(2) + Player.User._Mission + Convert.ToChar(2) + Player.User._Figure + Convert.ToChar(2) + Player.User._Sex + Convert.ToChar(2) + Encoding.encodeVL64(Player.teamID) + Encoding.encodeVL64(Player.roomUID)); p++; } Setup = new StringBuilder("I" + Encoding.encodeVL64(leftCountdownSeconds) + Encoding.encodeVL64(Config.Game_Countdown_Seconds) + Encoding.encodeVL64(p)); Setup.Append(Helper.ToString()); Helper = null; Setup.Append(Encoding.encodeVL64(bX) + Encoding.encodeVL64(bY)); for (int y = 0; y < bY; y++) for (int x = 0; x < bX; x++) Setup.Append(Encoding.encodeVL64((int)bbTColor[x, y]) + Encoding.encodeVL64((int)bbTState[x, y])); Setup.Append("IH"); } else Setup.Append("holo.cast.not_finished"); return Setup.ToString(); } private void updateLoop() { if (isBattleBall) { int teamAmount = Teams.Length; int[] teamScores = new int[teamAmount]; bool subtrSecond = false; int activeTeamAmount = 0; for (int i = 0; i < teamAmount; i++) { if (Teams[i].Count > 0) activeTeamAmount++; else teamScores[i] = -1; } Random RND = new Random(DateTime.Now.Millisecond * teamAmount); while (leftTime > 0) { int[] Amounts = new int[3]; // [0] = unit amount, [1] = updated tile amount, [2] = moving units amount StringBuilder Players = new StringBuilder(); StringBuilder updatedTiles = new StringBuilder(); StringBuilder Movements = new StringBuilder(); for (int i = 0; i < teamAmount; i++) foreach (gamePlayer Player in Teams[i]) { Players.Append("H" + Encoding.encodeVL64(Player.roomUID) + Encoding.encodeVL64(Player.X) + Encoding.encodeVL64(Player.Y) + Encoding.encodeVL64(Player.H) + Encoding.encodeVL64(Player.Z) + "HM"); if (Player.bbColorTile) { Player.bbColorTile = false; updatedTiles.Append(Encoding.encodeVL64(Player.X) + Encoding.encodeVL64(Player.Y) + Encoding.encodeVL64((int)bbTColor[Player.X, Player.Y]) + Encoding.encodeVL64((int)bbTState[Player.X, Player.Y])); Amounts[1]++; } if (Player.goalX != -1) { Coord Next = gamePathfinder.getNextStep(Player.X, Player.Y, Player.goalX, Player.goalY); if (Next.X != -1 && Blocked[Next.X, Next.Y] == false) { Amounts[2]++; Movements.Append("J" + Encoding.encodeVL64(Player.roomUID) + Encoding.encodeVL64(Next.X) + Encoding.encodeVL64(Next.Y)); if (Next.X == Player.goalX && Next.Y == Player.goalY) Player.goalX = -1; Blocked[Player.X, Player.Y] = false; Blocked[Next.X, Next.Y] = true; Player.Z = Rotation.Calculate(Player.X, Player.Y, Next.X, Next.Y); Player.X = Next.X; Player.Y = Next.Y; Player.H = Height[Next.X, Next.Y]; if (bbTState[Next.X, Next.Y] != bbTileState.Sealed) { if (bbTColor[Next.X, Next.Y] == (bbTileColor)Player.teamID) // Already property of this team, increment the state bbTState[Next.X, Next.Y]++; else { bbTState[Next.X, Next.Y] = bbTileState.Touched; bbTColor[Next.X, Next.Y] = (bbTileColor)Player.teamID; } int extraPoints = 0; switch ((int)bbTState[Next.X, Next.Y]) // Check the new status and calculate extra points { case 1: extraPoints = activeTeamAmount; break; case 2: extraPoints = activeTeamAmount * 3; break; case 3: extraPoints = activeTeamAmount * 5; break; case 4: extraPoints = activeTeamAmount * 7; break; } teamScores[Player.teamID] += extraPoints; Player.Score += extraPoints; Player.bbColorTile = true; } } else Player.goalX = -1; // Stop moving } Amounts[0]++; } string scoreString = "H" + Encoding.encodeVL64(teamAmount); for (int i = 0; i < teamAmount; i++) scoreString += Encoding.encodeVL64(teamScores[i]); sendData("Ct" + Encoding.encodeVL64(Amounts[0]) + Players.ToString() + Encoding.encodeVL64(Amounts[1]) + updatedTiles.ToString() + scoreString + "I" + Encoding.encodeVL64(Amounts[2]) + Movements.ToString()); if (subtrSecond) leftTime--; subtrSecond = (subtrSecond != true); Thread.Sleep(470); } State = gameState.Ended; StringBuilder Scores = new StringBuilder("Cx" + Encoding.encodeVL64(Config.Game_scoreWindow_restartGame_Seconds) + Encoding.encodeVL64(teamAmount)); for (int i = 0; i < teamAmount; i++) { int memberAmount = Teams[i].Count; if (memberAmount > 0) { Scores.Append(Encoding.encodeVL64(memberAmount)); using (DatabaseClient dbClient = Eucalypt.dbManager.GetClient()) { foreach (gamePlayer Player in Teams[i]) { Scores.Append(Encoding.encodeVL64(Player.roomUID)); if (Player.User != null) { dbClient.runQuery("UPDATE users SET bb_playedgames = bb_playedgames + 1,bb_totalpoints = bb_totalpoints + " + Player.Score + " WHERE id = '" + Player.User.userID + "' LIMIT 1"); Scores.Append(Player.User._Username + Convert.ToChar(2)); } else Scores.Append("M"); Scores.Append(Encoding.encodeVL64(Player.Score)); } } Scores.Append(Encoding.encodeVL64(teamScores[i])); } else Scores.Append("M"); } sendData(Scores.ToString()); } } private Coord[] getSurfaceTiles(Coord Start) { return null; } #endregion } }