/***********************************************************\
* 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
}