Click here to Skip to main content
15,892,517 members
Articles / Programming Languages / C#

UT2003 Gameserver Status

Rate me:
Please Sign up or sign in to vote.
5.00/5 (6 votes)
16 Oct 200216 min read 159K   923   40  
Getting the current Status of a UT2003 Gameserver via UDP Queries
  • ut2003query_src.zip
  • ut2003query_exec_pics.zip
    • mappics
      • br-anubis.jpg
      • br-bifrost.jpg
      • br-disclosure.jpg
      • br-icefields.jpg
      • br-skyline.jpg
      • br-slaughterhouse.jpg
      • br-twintombs.jpg
      • ctf-chrome.jpg
      • ctf-citadel.jpg
      • ctf-december.jpg
      • ctf-face3.jpg
      • ctf-geothermal.jpg
      • ctf-lostfaith.jpg
      • ctf-magma.jpg
      • ctf-maul.jpg
      • ctf-orbital2.jpg
      • dm-antalus.jpg
      • dm-asbestos.jpg
      • dm-compressed.jpg
      • dm-curse3.jpg
      • dm-flux2.jpg
      • dm-gael.jpg
      • dm-inferno.jpg
      • dm-insidious.jpg
      • dm-leviathan.jpg
      • dm-oceanic.jpg
      • dm-phobos2.jpg
      • dm-plunge.jpg
      • dm-serpentine.jpg
      • dm-tokaraforest.jpg
      • dm-trainingday.jpg
      • dom-core.jpg
      • dom-outrigger.jpg
      • dom-ruination.jpg
      • dom-scorchedearth.jpg
      • dom-sepukkugorge.jpg
      • dom-suntemple.jpg
    • QueryUT2003Server.exe
  • ut2003query_exec.zip
    • QueryUT2003Server.exe
  • ut2003query_mappics.zip
    • br-anubis.jpg
    • br-bifrost.jpg
    • br-disclosure.jpg
    • br-icefields.jpg
    • br-skyline.jpg
    • br-slaughterhouse.jpg
    • br-twintombs.jpg
    • ctf-chrome.jpg
    • ctf-citadel.jpg
    • ctf-december.jpg
    • ctf-face3.jpg
    • ctf-geothermal.jpg
    • ctf-lostfaith.jpg
    • ctf-magma.jpg
    • ctf-maul.jpg
    • ctf-orbital2.jpg
    • dm-antalus.jpg
    • dm-asbestos.jpg
    • dm-compressed.jpg
    • dm-curse3.jpg
    • dm-flux2.jpg
    • dm-gael.jpg
    • dm-inferno.jpg
    • dm-insidious.jpg
    • dm-leviathan.jpg
    • dm-oceanic.jpg
    • dm-phobos2.jpg
    • dm-plunge.jpg
    • dm-serpentine.jpg
    • dm-tokaraforest.jpg
    • dm-trainingday.jpg
    • dom-core.jpg
    • dom-outrigger.jpg
    • dom-ruination.jpg
    • dom-scorchedearth.jpg
    • dom-sepukkugorge.jpg
    • dom-suntemple.jpg
  • ut2003query_demo.zip
/***********************************************************\
 * Version 2.1 - Oct 16 2002                               *
\***********************************************************/

using System;
using System.IO;
using System.Net;
using System.Net.Sockets;
using System.Text;
using System.Collections;
using System.Collections.Specialized;
using System.Threading;

namespace QueryUT2003Server
{
	/// <summary>
	/// current Infos/Statistics of a Server running the UT2003-Demo
	/// </summary>
	public class UT2003Server
	{
		public static string [] TeamString = new string[4] {"red", "blue", "green", "gold"};

		public enum QueryProtocol {GameSpy,Unreal,Mixed};

		private IPAddress ip = IPAddress.None;
		private int port = -1;
		private int timeout = 1000; // 1 second default
		private int gameSpyQueryOffset = 10;
		private int unrealQueryOffset = 1;
		private QueryProtocol protocol = QueryProtocol.Mixed;
		private bool raiseNewPropExcept = false;
		// only used with GameSpy-Protocol
		private string answer;
		// only used with Unreal-Protocol
		private byte[] receiveBasic;
		private byte[] receiveServer;
		private byte[] receivePlayer;

		// server variables
		private string gamename;
		private int gamever;
		private int minnetver;
		private int location;
		private string hostname;
		private int hostport;
		private string maptitle;
		private string mapname;
		private string gametype;
		private int numplayers;
		private int maxplayers;
		private string servermode;
		private int minplayers;
		private bool balanceteams = true;
		private bool playersbalanceteams = true;
		private string friendlyfire;
		private string gamemode;
		private int timelimit;
		private int fraglimit;
		private int goalscore;
		private string adminname;
		private string adminemail;
		private string [] mutators;
		private bool gamestats = false;
		private string gamespeed;
		private bool password = false;
		private bool translocator = true;
		private Player[] players;

		private NameValueCollection serverVars = new NameValueCollection();

		#region QueryProperties
		/// <summary>
		/// Get/Set the servers IP-Address in standard dotted-quad format
		/// </summary>
		public string Ip
		{
			get { return ip.ToString(); }
			set { ip = IPAddress.Parse(value); serverVars.Clear(); }
		}

		/// <summary>
		/// Get/Set the servers official Port
		/// </summary>
		public int Port
		{
			get { return port; }
			set { port = value; }
		}

		/// <summary>
		/// Get/Set the receive-timeout in milliseconds (1000 == 1 sec)
		/// </summary>
		public int Timeout
		{
			get { return timeout; }
			set { timeout = value; }
		}

		/// <summary>
		/// Get/Set the offset (from the gameport) of the port number for the gamespy-query; default +10
		/// </summary>
		public int GameSpyQueryOffset
		{
			get { return gameSpyQueryOffset; }
			set { gameSpyQueryOffset = value; }
		}

		/// <summary>
		/// Get/Set the offset (from the gameport) of the port number for the unreal-query; default +1
		/// </summary>
		public int UnrealQueryOffset
		{
			get { return unrealQueryOffset; }
			set { unrealQueryOffset = value; }
		}

		/// <summary>
		/// Get/Set the Query Protocol
		/// </summary>
		public QueryProtocol Protocol
		{
			get { return protocol; }
			set { protocol = value; }
		}

		/// <summary>
		/// Get the complete last Answer in Gamespy-Query-Format (onyl if Protocol==GameSpy)
		/// </summary>
		public string Answer
		{
			get { return answer; }
		}

		/// <summary>
		/// Get the raw Basic Data received when using Unreal-Protocol
		/// </summary>
		public byte[] ReceivedBasic
		{
			get { return receiveBasic; }
		}

		/// <summary>
		/// Get the raw Server Data received when using Unreal-Protocol
		/// </summary>
		public byte[] ReceivedServer
		{
			get { return receiveServer; }
		}

		/// <summary>
		/// Get the raw Player Data received when using Unreal-Protocol
		/// </summary>
		public byte[] ReceivedPlayer
		{
			get { return receivePlayer; }
		}

		/// <summary>
		/// Get/Set if an exception should be raised, when an unhandled property is found;
		/// the class has to be extended by me in that case
		/// </summary>
		public bool RaiseExceptionOnUnhandledProperty
		{
			get { return raiseNewPropExcept; }
			set { raiseNewPropExcept=value; }
		}
		#endregion

		#region ServerVarsProperties
		/// <summary>
		/// Get all server properties in one collection
		/// </summary>
		public NameValueCollection ServerVars
		{
			get { return serverVars; }
		}

		/// <summary>
		/// Get the internal GameName-String ("ut2d" for UT2003-Demo)
		/// </summary>
		public string GameName
		{
			get { return gamename; }
		}

		/// <summary>
		/// Get the GameVersion the Server is running
		/// </summary>
		public int GameVersion
		{
			get { return gamever; }
		}

		/// <summary>
		/// Get the Minimum Version a Client needs to successfully connect to the server
		/// </summary>
		public int MinNetVersion
		{
			get { return minnetver; }
		}

		/// <summary>
		/// Get the Location the server is situated. rarely used by server-admins!
		/// </summary>
		/// <remarks>
		///  0 - No Region Specified (any Region)
		///  1 - Southeast US
		///  2 - Western US
		///  3 - Midwest US
		///  4 - Northwest US, West Canada
		///  5 - Northeast US, East Canada
		///  6 - United Kingdom
		///  7 - Continental Europe
		///  8 - Central Asia, Middle East
		///  9 - Southeast Asia, Pacific
		/// 10 - Africa
		/// 11 - Australia / NZ / Pacific
		/// 12 - Central, South America 
		/// </remarks>
		public int Location
		{
			get { return location; }
		}

		/// <summary>
		/// Get the Server's Name
		/// </summary>
		public string HostName
		{
			get { return hostname; }
		}

		/// <summary>
		/// Get the Title of the current Map, e.g. "Temple of Anubis"
		/// </summary>
		public string MapTitle
		{
			get { return maptitle; }
		}

		/// <summary>
		/// Get the Name of the current Map, e.g. "BR-Anubis"
		/// </summary>
		public string MapName
		{
			get { return mapname; }
		}

		/// <summary>
		/// Get the current Gametype running on the server
		/// </summary>
		public string GameType
		{
			get { return gametype; }
		}

		/// <summary>
		/// Get the Number of Players currently connected to the Server
		/// </summary>
		public int NumPlayers
		{
			get { return numplayers; }
		}

		/// <summary>
		/// Get the maximum Number of Players to be on the Server simultaneously
		/// </summary>
		public int MaxPlayers
		{
			get { return maxplayers; }
		}

		/// <summary>
		/// Get the minimum amount of players for the server to start a game
		/// </summary>
		public int MinPlayers
		{
			get { return minplayers; }
		}

		/// <summary>
		/// Get the server mode (for instance 'dedicated')
		/// </summary>
		public string ServerMode
		{
			get { return servermode; }
		}

		/// <summary>
		/// Delivers true if the server is to balance the teams
		/// </summary>
		public bool BalanceTeams
		{
			get { return balanceteams; }
		}

		/// <summary>
		/// Delivers true if the server is set to assign the players a team to balance the teams
		/// </summary>
		public bool PlayersBalanceTeams
		{
			get { return playersbalanceteams; }
		}

		/// <summary>
		/// Get the amount in percent a hit from a teammate hurts (in comparison to enemies)
		/// </summary>
		public string FriendlyFire
		{
			get { return friendlyfire; }
		}

		/// <summary>
		/// Get the Gamemode the Server currently runs in
		/// </summary>
		public string GameMode
		{
			get { return gamemode; }
		}

		/// <summary>
		/// Get the Time Limit for the current Game
		/// </summary>
		public int TimeLimit
		{
			get { return timelimit; }
		}

		/// <summary>
		/// Get the Frag Limit for the current Game
		/// </summary>
		public int FragLimit
		{
			get { return fraglimit; }
		}

		/// <summary>
		/// Get the score a team needs to win
		/// </summary>
		public int GoalScore
		{
			get { return goalscore; }
		}

		/// <summary>
		/// Get the Name of Administrator of the Server
		/// </summary>
		public string AdminName
		{
			get { return adminname; }
		}

		/// <summary>
		/// Get the Emailaddress of the Server-Administrator
		/// </summary>
		public string AdminEmail
		{
			get { return adminemail; }
		}

		/// <summary>
		/// Get the mutators active on that server
		/// </summary>
		public string [] Mutators
		{
			get { return mutators; }
		}

		/// <summary>
		/// Delivers true, if the server logs global statistics
		/// </summary>
		public bool GameStats
		{
			get { return gamestats; }
		}

		/// <summary>
		/// Get a gamespeed (1.0 is default 100%)
		/// </summary>
		public string GameSpeed
		{
			get { return gamespeed; }
		}

		/// <summary>
		/// Delivers true, if the server requires a password for playing
		/// </summary>
		public bool Password
		{
			get { return password; }
		}

		/// <summary>
		/// Delivers true, if players are allowed to use the translocator on this server
		/// </summary>
		public bool Translocator
		{
			get { return translocator; }
		}
		#endregion

		/// <summary>
		/// Get the Player-Array containing the currently active Players on the Server
		/// </summary>
		public Player[] Players
		{
			get { return players; }
		}

		#region Constructors
		/// <summary>
		/// constructs an uninitialized Server-Object - 
		/// remember to set IP and Port manually before the first Refresh!
		/// </summary>
		public UT2003Server()
		{
		}

		/// <summary>
		/// constructs a Server-Object
		/// </summary>
		/// <param name="ip">the IP-Address in standard dotted-quad format, e.g. "176.27.193.17"</param>
		/// <param name="port">the official Port of the server (*not* the Query-Port!)</param>
		public UT2003Server(string ip, int port)
		{
			this.ip = IPAddress.Parse(ip);
			this.port = port;
		}
		#endregion

		/// <summary>
		/// Connects to the Server and refreshes all Information
		/// </summary>
		public void Refresh()
		{
			if (ip==IPAddress.None || port<0)
				throw new ArgumentException("IP and/or port not set");

			TimedUdpClient udp = new TimedUdpClient();
			udp.Timeout = timeout;
			
			// remote is needed for receiving answers
			IPEndPoint remote;

			try
			{
				if (protocol == QueryProtocol.GameSpy || protocol == QueryProtocol.Mixed)
				{
					// set the server
					remote = new IPEndPoint(ip,port+gameSpyQueryOffset);
					// form the query
					byte[] query = Encoding.ASCII.GetBytes("\\status\\");
					// send the query
					udp.Send(query,query.Length,remote);
					// receive result
					answer = "";
					byte[] receive = null;
					DateTime start = DateTime.Now;
					TimeSpan diff;
					do
					{
						receive=udp.Receive(ref remote);
						// turn received byte array into a string
						if (receive!=null)
							answer += Encoding.ASCII.GetString(receive);
						DateTime now = DateTime.Now;
						diff = now-start;
					} while (!answer.EndsWith("\\final\\") && receive!=null && (diff.Seconds*1000+diff.Milliseconds < timeout));
					if (receive==null || (diff.Seconds*1000+diff.Milliseconds >= timeout))
						throw new ServerNotReachableException("timed out");
					// evaluate
					AnalyseGameSpyAnswer();
				}
				if (protocol == QueryProtocol.Unreal)
				{
					// basic query
					remote = new IPEndPoint(ip,port+unrealQueryOffset);
					byte [] queryBasic = {0x78,0x00,0x00,0x00,0x00};
					udp.Send(queryBasic,queryBasic.Length,remote);
					receiveBasic=udp.Receive(ref remote);
				}
				if (protocol == QueryProtocol.Unreal || protocol == QueryProtocol.Mixed)
				{
					// server props query
					remote = new IPEndPoint(ip,port+unrealQueryOffset);
					byte [] queryServer = {0x78,0x00,0x00,0x00,0x01};
					udp.Send(queryServer,queryServer.Length,remote);
					receiveServer=udp.Receive(ref remote);
				}
				if (protocol == QueryProtocol.Unreal)
				{
					// player props query
					remote = new IPEndPoint(ip,port+unrealQueryOffset);
					byte [] queryServer = {0x78,0x00,0x00,0x00,0x02};
					udp.Send(queryServer,queryServer.Length,remote);
					receivePlayer=udp.Receive(ref remote);
				}
				if (protocol == QueryProtocol.Unreal || protocol == QueryProtocol.Mixed)
				{
					if (protocol == QueryProtocol.Mixed)
					{
						// overwrite possible old answers, as these should be ignored
						// in AnalyseUnrealAnswer()
						receiveBasic = null;
						receivePlayer = null;
					}
					//evaluate
					AnalyseUnrealAnswer();
				}
			}
			finally
			{
				udp.Close();
			}
		}

		/// <summary>
		/// takes a server-property name and translates it into a human readable term (if known)
		/// </summary>
		/// <param name="propname">the property name</param>
		/// <returns>the human readable term</returns>
		public static string Prop2En(string propname)
		{
			switch (propname)
			{
				case "gamename":			return "Game Name";
				case "gamever":				return "Game Version";
				case "minnetver":			return "Min Client Version";
				case "location":			return "Location";
				case "hostname":			return "Server Name";
				case "hostport":			return "Game Port";
				case "maptitle":			return "Map Title";
				case "mapname":				return "Map Filename";
				case "gametype":			return "Game Type";
				case "numplayers":			return "Active Players";
				case "maxplayers":			return "Max Players";
				case "servermode":			return "Server Mode";
				case "minplayers":			return "Min Players";
				case "balanceteams":		return "Balance Teams";
				case "playersbalanceteams": return "Players Balance Teams";
				case "friendlyfire":		return "Friendly Fire";
				case "gamemode":			return "Game Mode";
				case "timelimit":			return "Time Limit";
				case "fraglimit":			return "Frag Limit";
				case "goalscore":			return "Goal Score";
				case "adminname":			return "Admin Name";
				case "adminemail":			return "Admin Email";
				case "mutator":				return "Mutators";
				case "gamestats":			return "Stats Logging";
				case "gamespeed":			return "Game Speed";
				case "password":			return "Password protected";
				case "translocator":		return "Translocator enabled";
			}
			return propname;
		}

		/// <summary>
		/// parses the answer-member and assigns the server- and player-variables the corresponding values
		/// </summary>
		private void AnalyseGameSpyAnswer()
		{
			string[] items = answer.Split(new char[] {'\\'});

			ArrayList tempPlayers = new ArrayList();

			int pos = 1; // the first item is always "", since answer starts with a backslash
			int lastQid = 0;
			int lastPlayerIndex = -1; // needed for backslash-bug workaround
			int oldpos; // helps detecting unhandled properties
			while (pos < items.Length)
			{
				oldpos = pos;
				while (lastPlayerIndex >= 0 && 
					(items[pos].Length<7 || items[pos].Substring(0,6)!="frags_") &&
					(items[pos].Length<7 || items[pos].Substring(0,7)!="queryid"))
				{
					((Player)tempPlayers[lastPlayerIndex]).Name += "\\"+items[pos++];
				}
				lastPlayerIndex = -1;
				switch (items[pos].ToLower())
				{
					// server variables
					case "gamename" : gamename = items[++pos]; serverVars["gamename"]=gamename; break;
					case "gamever": gamever = int.Parse(items[++pos]); serverVars["gamever"]=gamever.ToString(); break;
					case "minnetver": minnetver = int.Parse(items[++pos]); serverVars["minnetver"]=minnetver.ToString(); break;
					case "location": location = int.Parse(items[++pos]); serverVars["location"]=location.ToString(); break;
					case "hostname": hostname = items[++pos]; serverVars["hostname"]=hostname; break;
					case "hostport": hostport = int.Parse(items[++pos]); serverVars["hostport"]=hostport.ToString(); break;
					case "maptitle": maptitle = items[++pos]; serverVars["maptitle"]=maptitle; break;
					case "mapname": mapname = items[++pos]; serverVars["mapname"]=mapname; break;
					case "gametype": gametype = items[++pos]; serverVars["gametype"]=gametype; break;
					case "numplayers": numplayers = int.Parse(items[++pos]); serverVars["numplayers"]=numplayers.ToString(); break;
					case "maxplayers": maxplayers = int.Parse(items[++pos]); serverVars["maxplayers"]=maxplayers.ToString(); break;
					case "gamemode": gamemode = items[++pos]; serverVars["gamemode"]=gamemode; break;
					case "timelimit": timelimit = int.Parse(items[++pos]); serverVars["timelimit"]=timelimit.ToString(); break;
					case "fraglimit": fraglimit = int.Parse(items[++pos]); serverVars["fraglimit"]=fraglimit.ToString(); break;
					case "adminname": adminname = items[++pos]; serverVars["adminname"]=adminname; break;
					case "adminemail": adminemail = items[++pos]; serverVars["adminemail"]=adminemail; break;
					case "password": password = items[++pos].ToLower()=="true"; serverVars["password"]=password.ToString(); break;
					// check package order
					case "queryid":
						if (lastQid!=0)
						{
							int thisQid = 
								int.Parse(items[++pos].Substring(0,items[pos].IndexOf('.')))*10 + 
								int.Parse(items[pos].Substring(items[pos].IndexOf('.')+1));
							if (thisQid == lastQid + 1)
								lastQid = thisQid;
							else
							{
								string message = String.Format("Package {$0} is missing.",(lastQid+1)%10);
								throw new PackagesOutOfOrderException(message);
							}
						}
						else
						{
							lastQid = 
								int.Parse(items[++pos].Substring(0,items[pos].IndexOf('.')))*10 + 
								int.Parse(items[pos].Substring(items[pos].IndexOf('.')+1));
							if (lastQid%10!=1)
							{
								string message = String.Format("First package is missing.");
								throw new PackagesOutOfOrderException(message);
							}
						}
						break;
				}
				if (items[pos].Length > 7 && items[pos].Substring(0,7)=="player_")
				{
					int index = int.Parse(items[pos].Substring(7));
					while (tempPlayers.Count <= index)
						tempPlayers.Add(new Player());
					((Player)tempPlayers[index]).Name = items[++pos];
					lastPlayerIndex = index;
				}
				if (items[pos].Length > 6 && items[pos].Substring(0,6)=="frags_")
				{
					int index = int.Parse(items[pos].Substring(6));
					while (tempPlayers.Count <= index)
						tempPlayers.Add(new Player());
					((Player)tempPlayers[index]).Frags = int.Parse(items[++pos]);
				}
				if (items[pos].Length > 5 && items[pos].Substring(0,5)=="ping_")
				{
					int index = int.Parse(items[pos].Substring(5));
					while (tempPlayers.Count <= index)
						tempPlayers.Add(new Player());
					// nasty workaround for players delivered without a ping value - f***ing gamespy-protocol :\
					// maybe spectators?
					try
					{
						((Player)tempPlayers[index]).Ping = int.Parse(items[++pos]);
					}
					catch (System.FormatException)
					{
						((Player)tempPlayers[index]).Ping = 0;
					}
				}
				if (items[pos].Length > 5 && items[pos].Substring(0,5)=="team_")
				{
					int index = int.Parse(items[pos].Substring(5));
					while (tempPlayers.Count <= index)
						tempPlayers.Add(new Player());
					// querying a ut2003-demo server generates a FormatException in int.Parse
					try
					{
						((Player)tempPlayers[index]).Team = TeamString[int.Parse(items[++pos])];
					}
					catch (System.FormatException)
					{
						((Player)tempPlayers[index]).Team = "";
					}
				}

				// for debugging reasons
				if (raiseNewPropExcept)
				{
					if (pos==oldpos && items[pos]!="final" && items[pos].Length>0)
						throw new AnswerFormatException("unhandled property: "+items[pos]);
				}

				++pos;
			}
			players = new Player[tempPlayers.Count];
			for (int i=0; i<tempPlayers.Count; ++i)
				players[i] = (Player)tempPlayers[i];
		}

		/// <summary>
		/// parses the 3 received*-byte-arrays and assigns the server- and player-variables the corresponding values
		/// </summary>
		private void AnalyseUnrealAnswer()
		{
			int pos=0;
			if (receiveBasic!=null)
			{
				pos=18; // first 18 bytes ignored
				hostname = ExtractString(receiveBasic,ref pos);
				serverVars["hostname"]=hostname;
				mapname = ExtractString(receiveBasic,ref pos);
				serverVars["mapname"]=mapname;
				gametype = ExtractString(receiveBasic, ref pos);
				serverVars["gametype"]=gametype;
				numplayers = ExtractInt(receiveBasic,ref pos);
				serverVars["numplayers"]=numplayers.ToString();
				maxplayers = ExtractInt(receiveBasic,ref pos);
				serverVars["maxplayers"]=maxplayers.ToString();
			}

			pos=0;
			if (receiveServer!=null)
			{
				pos=5; // first 5 bytes ignored
				System.Text.StringBuilder mutatorset = new System.Text.StringBuilder("");
				if (serverVars["mutator"]!=null)
					serverVars["mutator"]="";
				while (pos < receiveServer.Length && receiveServer[pos]!=0x00)
				{
					string property = ExtractString(receiveServer,ref pos);
					property = property.ToLower();
					string thevalue = ExtractString(receiveServer,ref pos);
					if (property=="mutator" && serverVars["mutator"]!="")
						serverVars.Add("mutator",thevalue);
					else
						serverVars[property]=thevalue;
					switch (property)
					{
						case "servermode": servermode = thevalue; break;
						case "adminname": adminname = thevalue; break;
						case "adminemail": adminemail = thevalue; break;
						case "gamestats": gamestats = thevalue.ToLower()=="true"; break;
						case "gamespeed": gamespeed = thevalue; break;
						case "password": password = thevalue.ToLower()=="true"; break;
						case "mutator": mutatorset.Append(" "+thevalue); break;
						case "goalscore": goalscore = int.Parse(thevalue); break;
						case "timelimit": timelimit = int.Parse(thevalue); break;
						case "minplayers": minplayers = int.Parse(thevalue); break;
						case "translocator": translocator = thevalue.ToLower()=="true"; break;
						case "balanceteams": balanceteams = thevalue.ToLower()=="true"; break;
						case "playersbalanceteams": playersbalanceteams = thevalue.ToLower()=="true"; break;
						case "friendlyfire": friendlyfire = thevalue; break;
						default: 
							if (raiseNewPropExcept) 
								 throw new AnswerFormatException("unhandled property: "+property); 
							break;
					}
					// for debugging reasons
				}
				if (mutatorset.Length>0)
				{
					mutatorset.Remove(0,1);
					mutators = mutatorset.ToString().Split(new char[] {' '});
				}
				else
					mutators = new string[0];
			}

			pos=0;
			if (receivePlayer!=null)
			{
				ArrayList tempPlayers = new ArrayList();
				pos=5; // first 5 bytes ignored
				while (pos < receivePlayer.Length)
				{
					Player p = new Player();
					p.Id = ExtractInt(receivePlayer,ref pos);
					p.Name = ExtractString(receivePlayer,ref pos);
					p.Ping = ExtractInt(receivePlayer,ref pos);
					p.Frags = ExtractInt(receivePlayer,ref pos);
					p.StatsId = ExtractInt(receivePlayer,ref pos);
					tempPlayers.Add(p);
				}
				players = new Player[tempPlayers.Count];
				for (int i=0; i<tempPlayers.Count; ++i)
					players[i] = (Player)tempPlayers[i];
			}
		}

		/// <summary>
		/// extracts a string from a byte-array, the string-representation starts with a byte telling the length
		/// including the trailing null-character
		/// </summary>
		/// <param name="b">the byte array</param>
		/// <param name="pos">position of the byte, that tells the length of the string (including final-\0)</param>
		/// <returns>the extracted string, pos is set to the first character behind the string</returns>
		private string ExtractString(byte[] b,ref int pos)
		{
			if (b.Length < pos+b[pos])
				throw new AnswerFormatException("answer too short");

			StringBuilder str = new StringBuilder((int)b[pos]);
			char [] c;
			if (b[pos]>1)
				c = new Char[b[pos]-1];
			else
				c = new Char[b[pos]];
			for (int i=0; i<b[pos]-1; ++i)
				c[i] = (char)b[pos+1+i];
			str.Append(c);
			pos+=b[pos]+1;
			return str.ToString();
		}

		/// <summary>
		/// extracts an 4-byte integer out of the byte array, lowest byte should be first
		/// </summary>
		/// <param name="b">the byte array</param>
		/// <param name="pos">position of the first (lowest) byte</param>
		/// <returns>the extracted int, pos is set to the first character behind the integer (=pos+4)</returns>
		private int ExtractInt(byte[] b, ref int pos)
		{
			if (b.Length < pos+4)
				throw new AnswerFormatException("answer too short");

			int ret = 0;
			ret += b[pos++];
			ret += b[pos++]<<8;
			ret += b[pos++]<<16;
			ret += b[pos++]<<24;
			return ret;
		}

		/// <summary>
		/// Queries the master server for gameserver running the retail version
		/// </summary>
		/// <returns>a UT2003Server-array containing all UT2003 retail servers</returns>
		public static UT2003Server [] QueryMaster()
		{
			return GetServerList("http://ut2003master.epicgames.com/serverlist/full-all.txt");
		}

		/// <summary>
		/// Queries the master server for gameserver running the demo version
		/// </summary>
		/// <returns>a UT2003Server-array containing all UT2003 demo servers</returns>
		public static UT2003Server [] QueryDemoMaster()
		{
			return GetServerList("http://ut2003master.epicgames.com/serverlist/demo-all.txt");
		}

		private static UT2003Server [] GetServerList(string url)
		{
			Stream stream = null;
			ArrayList servers = new ArrayList();

			WebRequest req = WebRequest.Create(url);
			WebResponse resp = req.GetResponse();
			stream = resp.GetResponseStream();
			StreamReader reader = new StreamReader(stream,System.Text.Encoding.UTF8);
			string line;
			while ((line=reader.ReadLine())!=null)
			{
				string [] values = line.Split(null,3);
				UT2003Server server = new UT2003Server(values[0],int.Parse(values[1]));
				server.GameSpyQueryOffset = int.Parse(values[2]) - int.Parse(values[1]);
				servers.Add(server);
			}

			UT2003Server [] serverArray = new UT2003Server[servers.Count];
			for (int i=0; i<servers.Count; ++i)
			{
				serverArray[i] = (UT2003Server)servers[i];
			}
			return serverArray;
		}
	}

	/// <summary>
	/// embraces one players properties
	/// </summary>
	public class Player
	{
		private string name;
		private int frags;
		private int ping;
		private string team;
		private int id;
		private int statsid;

		#region Properties
		/// <summary>
		/// Get/Set a Players Name
		/// </summary>
		public string Name
		{
			get { return name; }
			set { name = value; }
		}

		/// <summary>
		/// Get/Set a Players Frag Count
		/// </summary>
		public int Frags
		{
			get { return frags; }
			set { frags = value; }
		}

		/// <summary>
		/// Get/Set a Players Ping
		/// </summary>
		public int Ping
		{
			get { return ping; }
			set { ping = value; }
		}

		/// <summary>
		/// Get/Set a Players Team
		/// </summary>
		public string Team
		{
			get { return team; }
			set { team = value; }
		}

		/// <summary>
		/// Get/Set the Players Id on the Server
		/// </summary>
		public int Id
		{
			get { return id; }
			set { id = value; }
		}

		/// <summary>
		/// Get/Set the Players global Stats-Id
		/// </summary>
		public int StatsId
		{
			get { return statsid; }
			set { statsid = value; }
		}
		#endregion

		/// <summary>
		/// standard constructor - does nothing, all members are null/0 by default
		/// </summary>
		public Player()
		{
		}
	}

	/// <summary>
	/// a UdpClient with an timer-enhanced Receive function
	/// </summary>
	public class TimedUdpClient : UdpClient
	{
		private int timeout = 1000; // 1 sec default
		private Thread timeoutWatchdog;

		/// <summary>
		/// Get/Set the receiving timeout in milliseconds (1000 = 1sec)
		/// </summary>
		public int Timeout
		{
			get { return timeout; }
			set { timeout = value; }
		}

		/// <summary>
		/// Returns a UDP Datagram that was sent by a remote host, but waits only a certain amount of time
		/// </summary>
		/// <param name="remote">representing the remote host, from which the data was sent</param>
		/// <returns>the datagram if successful, null if the timer expired</returns>
		public new byte[] Receive(ref IPEndPoint remote)
		{
			byte [] ret;
			timeoutWatchdog = new Thread(new ThreadStart(StartWatchdog));
			timeoutWatchdog.Start();
			try
			{
				ret = base.Receive(ref remote);
			}
			catch (SocketException)
			{
				ret=null;
			}
			finally
			{
				timeoutWatchdog.Abort();
			}
			return ret;
		}

		private void StartWatchdog()
		{
			Thread.Sleep(timeout);
			this.Send(new byte[] {0x00},1,"",8000);
		}
	}

	#region Exceptions
	/// <summary>
	/// this is the base-class for an easy catch in your application, this is not used
	/// directly, rather it's child-classes are
	/// </summary>
	public class GameserverQueryException : ApplicationException
	{
		public GameserverQueryException()
		{
		}

		public GameserverQueryException(string message)
			:base(message)
		{
		}

		public GameserverQueryException(string message, Exception inner)
			:base(message, inner)
		{
		}
	}	

	/// <summary>
	/// since UDP is a connection-less protocol without any asurance, packages may arrive out of order
	/// </summary>
	public class PackagesOutOfOrderException : GameserverQueryException
	{
		public PackagesOutOfOrderException()
		{
		}

		public PackagesOutOfOrderException(string message)
			:base(message)
		{
		}

		public PackagesOutOfOrderException(string message, Exception inner)
			:base(message, inner)
		{
		}
	}	

	/// <summary>
	/// since UDP is a connection-less protocol without any asurance, packages may arrive out of order
	/// </summary>
	public class ServerNotReachableException : GameserverQueryException
	{
		public ServerNotReachableException()
		{
		}

		public ServerNotReachableException(string message)
			:base(message)
		{
		}

		public ServerNotReachableException(string message, Exception inner)
			:base(message, inner)
		{
		}
	}	

	/// <summary>
	/// since UDP is a connection-less protocol without any asurance, packages may arrive out of order
	/// </summary>
	public class AnswerFormatException : GameserverQueryException
	{
		public AnswerFormatException()
		{
		}

		public AnswerFormatException(string message)
			:base(message)
		{
		}

		public AnswerFormatException(string message, Exception inner)
			:base(message, inner)
		{
		}
	}	
	#endregion
}

By viewing downloads associated with this article you agree to the Terms of Service and the article's licence.

If a file you wish to view isn't highlighted, and is a text file (not binary), please let us know and we'll add colourisation support for it.

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here


Written By
Web Developer
Germany Germany
This member has not yet provided a Biography. Assume it's interesting and varied, and probably something to do with programming.

Comments and Discussions