steam-condenser 0.8.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (72) hide show
  1. data/LICENSE +25 -0
  2. data/README.md +30 -0
  3. data/Rakefile +42 -0
  4. data/lib/abstract_class.rb +40 -0
  5. data/lib/byte_buffer.rb +126 -0
  6. data/lib/datagram_channel.rb +70 -0
  7. data/lib/exceptions/buffer_underflow_exception.rb +10 -0
  8. data/lib/exceptions/packet_format_exception.rb +12 -0
  9. data/lib/exceptions/rcon_no_auth_exception.rb +14 -0
  10. data/lib/exceptions/steam_condenser_exception.rb +8 -0
  11. data/lib/exceptions/timeout_exception.rb +10 -0
  12. data/lib/socket_channel.rb +54 -0
  13. data/lib/steam-condenser.rb +3 -0
  14. data/lib/steam/community/dods/dods_class.rb +32 -0
  15. data/lib/steam/community/dods/dods_stats.rb +47 -0
  16. data/lib/steam/community/dods/dods_weapon.rb +54 -0
  17. data/lib/steam/community/game_achievement.rb +26 -0
  18. data/lib/steam/community/game_class.rb +15 -0
  19. data/lib/steam/community/game_stats.rb +92 -0
  20. data/lib/steam/community/game_weapon.rb +29 -0
  21. data/lib/steam/community/l4d/l4d_explosive.rb +23 -0
  22. data/lib/steam/community/l4d/l4d_map.rb +34 -0
  23. data/lib/steam/community/l4d/l4d_stats.rb +166 -0
  24. data/lib/steam/community/l4d/l4d_weapon.rb +23 -0
  25. data/lib/steam/community/steam_group.rb +16 -0
  26. data/lib/steam/community/steam_id.rb +212 -0
  27. data/lib/steam/community/tf2/tf2_class.rb +31 -0
  28. data/lib/steam/community/tf2/tf2_class_factory.rb +38 -0
  29. data/lib/steam/community/tf2/tf2_engineer.rb +25 -0
  30. data/lib/steam/community/tf2/tf2_medic.rb +22 -0
  31. data/lib/steam/community/tf2/tf2_sniper.rb +20 -0
  32. data/lib/steam/community/tf2/tf2_spy.rb +23 -0
  33. data/lib/steam/community/tf2/tf2_stats.rb +39 -0
  34. data/lib/steam/packets/a2a_ack_packet.rb +21 -0
  35. data/lib/steam/packets/a2a_ping_packet.rb +18 -0
  36. data/lib/steam/packets/a2m_get_servers_batch2_packet.rb +25 -0
  37. data/lib/steam/packets/a2s_info_packet.rb +18 -0
  38. data/lib/steam/packets/a2s_player_packet.rb +22 -0
  39. data/lib/steam/packets/a2s_rules_packet.rb +21 -0
  40. data/lib/steam/packets/a2s_serverquery_getchallenge_packet.rb +18 -0
  41. data/lib/steam/packets/m2a_server_batch_packet.rb +37 -0
  42. data/lib/steam/packets/rcon/rcon_auth_request.rb +16 -0
  43. data/lib/steam/packets/rcon/rcon_auth_response.rb +16 -0
  44. data/lib/steam/packets/rcon/rcon_exec_request.rb +16 -0
  45. data/lib/steam/packets/rcon/rcon_exec_response.rb +20 -0
  46. data/lib/steam/packets/rcon/rcon_goldsrc_request.rb +20 -0
  47. data/lib/steam/packets/rcon/rcon_goldsrc_response.rb +20 -0
  48. data/lib/steam/packets/rcon/rcon_packet.rb +36 -0
  49. data/lib/steam/packets/rcon/rcon_packet_factory.rb +37 -0
  50. data/lib/steam/packets/request_with_challenge.rb +16 -0
  51. data/lib/steam/packets/s2a_info2_packet.rb +48 -0
  52. data/lib/steam/packets/s2a_info_base_packet.rb +29 -0
  53. data/lib/steam/packets/s2a_info_detailed_packet.rb +50 -0
  54. data/lib/steam/packets/s2a_player_packet.rb +32 -0
  55. data/lib/steam/packets/s2a_rules_packet.rb +33 -0
  56. data/lib/steam/packets/s2c_challenge_packet.rb +23 -0
  57. data/lib/steam/packets/steam_packet.rb +48 -0
  58. data/lib/steam/packets/steam_packet_factory.rb +90 -0
  59. data/lib/steam/servers/game_server.rb +222 -0
  60. data/lib/steam/servers/goldsrc_server.rb +36 -0
  61. data/lib/steam/servers/master_server.rb +60 -0
  62. data/lib/steam/servers/source_server.rb +65 -0
  63. data/lib/steam/sockets/goldsrc_socket.rb +108 -0
  64. data/lib/steam/sockets/master_server_socket.rb +20 -0
  65. data/lib/steam/sockets/rcon_socket.rb +50 -0
  66. data/lib/steam/sockets/source_socket.rb +72 -0
  67. data/lib/steam/sockets/steam_socket.rb +63 -0
  68. data/lib/steam/steam_player.rb +51 -0
  69. data/test/query_tests.rb +71 -0
  70. data/test/rcon_tests.rb +79 -0
  71. data/test/steam_community_tests.rb +36 -0
  72. metadata +132 -0
@@ -0,0 +1,29 @@
1
+ # This code is free software; you can redistribute it and/or modify it under the
2
+ # terms of the new BSD License.
3
+
4
+ require "abstract_class"
5
+ require "steam/packets/steam_packet"
6
+
7
+ # The S2A_INFO_BasePacket class represents the response to a A2S_INFO
8
+ # request send to the server.
9
+ class S2A_INFO_BasePacket < SteamPacket
10
+
11
+ include AbstractClass
12
+
13
+ # Creates a S2A_INFO[...] response object based on the data received.
14
+ def initialize(header, data)
15
+ super header, data
16
+ end
17
+
18
+ # Returns the hash containing information on the server
19
+ public
20
+ def get_info_hash
21
+ return Hash[
22
+ *instance_variables.map { |var|
23
+ if var != "@content_data" && var != "@header_data"
24
+ [var[1..-1], instance_variable_get(var)]
25
+ end
26
+ }.compact.flatten
27
+ ]
28
+ end
29
+ end
@@ -0,0 +1,50 @@
1
+ # This code is free software; you can redistribute it and/or modify it under the
2
+ # terms of the new BSD License.
3
+ #
4
+ # Copyright (c) 2008-2009, Sebastian Staudt
5
+
6
+ require "steam/packets/s2a_info_base_packet"
7
+
8
+ # The S2A_INFO_DETAILED_Packet class represents the response to a A2S_INFO
9
+ # request send to a GoldSrc server.
10
+ # This is deprecated by 10/24/2008 for GoldSrc servers. They use the same
11
+ # format as Source servers (S2A_INFO2) now.
12
+ class S2A_INFO_DETAILED_Packet < S2A_INFO_BasePacket
13
+
14
+ # Creates a S2A_INFO_DETAILED response object based on the data received.
15
+ def initialize(data)
16
+ super SteamPacket::S2A_INFO_DETAILED_HEADER, data
17
+
18
+ @game_ip = @content_data.get_string
19
+ @server_name = @content_data.get_string
20
+ @map_name = @content_data.get_string
21
+ @game_directory = @content_data.get_string
22
+ @game_description = @content_data.get_string
23
+ @number_of_players = @content_data.get_byte
24
+ @max_players = @content_data.get_byte
25
+ @network_version = @content_data.get_byte
26
+ @dedicated = @content_data.get_byte.chr
27
+ @operating_system = @content_data.get_byte.chr
28
+ @password_needed = @content_data.get_byte == 1
29
+ @is_mod = @content_data.get_byte == 1
30
+
31
+ if @is_mod
32
+ @mod_info = Hash.new
33
+ @mod_info["url_info"] = @content_data.get_string
34
+ @mod_info["url_dl"] = @content_data.get_string
35
+ @content_data.get_byte
36
+ if @content_data.remaining == 12
37
+ @mod_info["mod_version"] = @content_data.get_long
38
+ @mod_info["mod_size"] = @content_data.get_long
39
+ @mod_info["sv_only"] = @content_data.get_byte == 1
40
+ @mod_info["cl_dll"] = @content_data.get_byte == 1
41
+ @secure = @content_data.get_byte == 1
42
+ @number_of_bots = @content_data.get_byte
43
+ end
44
+ else
45
+ @secure = @content_data.get_byte == 1
46
+ @number_of_bots = @content_data.get_byte
47
+ end
48
+ end
49
+
50
+ end
@@ -0,0 +1,32 @@
1
+ # This code is free software; you can redistribute it and/or modify it under the
2
+ # terms of the new BSD License.
3
+ #
4
+ # Copyright (c) 2008-2009, Sebastian Staudt
5
+
6
+ require "steam/packets/steam_packet"
7
+
8
+ # The S2A_PLAYER_Packet class represents the response to a A2S_PLAYER
9
+ # request send to the server.
10
+ class S2A_PLAYER_Packet < SteamPacket
11
+
12
+ attr_reader :player_hash
13
+
14
+ # Creates a S2A_PLAYER response object based on the data received.
15
+ def initialize(content_data)
16
+ if content_data == nil
17
+ raise Exception.new("Wrong formatted S2A_PLAYER packet.")
18
+ end
19
+
20
+ super SteamPacket::S2A_PLAYER_HEADER, content_data
21
+
22
+ # Ignore player count
23
+ @content_data.get_byte
24
+ @player_hash = {}
25
+
26
+ while @content_data.remaining > 0
27
+ player_data = @content_data.get_byte, @content_data.get_string, @content_data.get_signed_long, @content_data.get_float
28
+ @player_hash[player_data[1]] = SteamPlayer.new(*player_data[0..3])
29
+ end
30
+ end
31
+
32
+ end
@@ -0,0 +1,33 @@
1
+ # This code is free software; you can redistribute it and/or modify it under the
2
+ # terms of the new BSD License.
3
+ #
4
+ # Copyright (c) 2008, Sebastian Staudt
5
+ #
6
+ # $Id$
7
+
8
+ require "steam/packets/steam_packet"
9
+
10
+ # The S2A_RULES_Packet class represents the response to a A2S_RULES
11
+ # request send to the server.
12
+ class S2A_RULES_Packet < SteamPacket
13
+
14
+ # Creates a S2A_RULES response object based on the data received.
15
+ def initialize(content_data)
16
+ if content_data == nil
17
+ raise Exception.new("Wrong formatted A2A_RULES response packet.")
18
+ end
19
+
20
+ super SteamPacket::S2A_RULES_HEADER, content_data
21
+
22
+ @rules_hash = Hash.new @content_data.get_short
23
+
24
+ while @content_data.remaining > 0
25
+ @rules_hash[@content_data.get_string] = @content_data.get_string
26
+ end
27
+ end
28
+
29
+ # Returns the hash containing the server rules
30
+ def get_rules_hash
31
+ return @rules_hash
32
+ end
33
+ end
@@ -0,0 +1,23 @@
1
+ # This code is free software; you can redistribute it and/or modify it under the
2
+ # terms of the new BSD License.
3
+ #
4
+ # Copyright (c) 2008, Sebastian Staudt
5
+ #
6
+ # $Id$
7
+
8
+ require "steam/packets/steam_packet"
9
+
10
+ # The S2C_CHALLENGE_Packet class represents the response
11
+ # to a A2S_SERVERQUERY_GETCHALLENGE request send to the server.
12
+ class S2C_CHALLENGE_Packet < SteamPacket
13
+
14
+ # Creates a AS2C_CHALLENGE response object based on the data received.
15
+ def initialize(challenge_number)
16
+ super S2C_CHALLENGE_HEADER, challenge_number
17
+ end
18
+
19
+ # Returns the challenge number received from the server
20
+ def get_challenge_number
21
+ return @content_data.rewind.get_long
22
+ end
23
+ end
@@ -0,0 +1,48 @@
1
+ # This code is free software; you can redistribute it and/or modify it under the
2
+ # terms of the new BSD License.
3
+ #
4
+ # Copyright (c) 2008, Sebastian Staudt
5
+ #
6
+ # $Id$
7
+
8
+ # This class represents a packet used by the Source query protocol
9
+ class SteamPacket
10
+ S2A_INFO_DETAILED_HEADER = 0x6D
11
+ A2S_INFO_HEADER = 0x54
12
+ S2A_INFO2_HEADER = 0x49
13
+ A2A_PING_HEADER = 0x69
14
+ A2A_ACK_HEADER = 0x6A
15
+ A2S_PLAYER_HEADER = 0x55
16
+ S2A_PLAYER_HEADER = 0x44
17
+ A2S_RULES_HEADER = 0x56
18
+ S2A_RULES_HEADER = 0x45
19
+ A2S_SERVERQUERY_GETCHALLENGE_HEADER = 0x57
20
+ S2C_CHALLENGE_HEADER = 0x41
21
+ A2M_GET_SERVERS_BATCH2_HEADER = 0x31
22
+ M2A_SERVER_BATCH_HEADER = 0x66
23
+ S2C_CONNREJECT_HEADER = 0x39
24
+ RCON_GOLDSRC_CHALLENGE_HEADER = 0x63
25
+ RCON_GOLDSRC_NO_CHALLENGE_HEADER = 0x39
26
+ RCON_GOLDSRC_RESPONSE_HEADER = 0x6c
27
+
28
+ # Creates a new SteamPacket object with given header and content data
29
+ def initialize(header_data, content_data = "")
30
+ @content_data = ByteBuffer.new content_data
31
+ @header_data = header_data
32
+ end
33
+
34
+ # Returns a packed string representing the packet's data
35
+ #
36
+ # TODO Has to automatically split packets greater than 1400 bytes
37
+ def to_s
38
+ packet_data = [0xFF, 0xFF, 0xFF].pack("ccc")
39
+
40
+ unless @split_packet
41
+ packet_data << [0xFF].pack("c")
42
+ else
43
+ packet_data << [0xFE].pack("c")
44
+ end
45
+
46
+ return packet_data << [@header_data, @content_data.array].pack("ca*")
47
+ end
48
+ end
@@ -0,0 +1,90 @@
1
+ # This code is free software; you can redistribute it and/or modify it under the
2
+ # terms of the new BSD License.
3
+ #
4
+ # Copyright (c) 2008-2009, Sebastian Staudt
5
+
6
+ require 'zlib'
7
+
8
+ require "abstract_class"
9
+ require "steam/packets/s2a_info_detailed_packet"
10
+ require "steam/packets/a2s_info_packet"
11
+ require "steam/packets/s2a_info2_packet"
12
+ require "steam/packets/a2a_ping_packet"
13
+ require "steam/packets/a2a_ack_packet"
14
+ require "steam/packets/a2s_player_packet"
15
+ require "steam/packets/s2a_player_packet"
16
+ require "steam/packets/a2s_rules_packet"
17
+ require "steam/packets/s2a_rules_packet"
18
+ require "steam/packets/a2s_serverquery_getchallenge_packet"
19
+ require "steam/packets/s2c_challenge_packet"
20
+ require "steam/packets/a2m_get_servers_batch2_packet"
21
+ require "steam/packets/m2a_server_batch_packet"
22
+ require "steam/packets/rcon/rcon_goldsrc_response"
23
+
24
+ class SteamPacketFactory
25
+
26
+ include AbstractClass
27
+
28
+ # Creates a new packet object based on the header byte of the given raw data
29
+ def self.get_packet_from_data(raw_data)
30
+ header = raw_data[0].to_i;
31
+ data = raw_data[1..-1];
32
+
33
+ case header
34
+ when SteamPacket::S2A_INFO_DETAILED_HEADER
35
+ return S2A_INFO_DETAILED_Packet.new(data)
36
+ when SteamPacket::A2S_INFO_HEADER
37
+ return A2S_INFO_Packet.new;
38
+ when SteamPacket::S2A_INFO2_HEADER
39
+ return S2A_INFO2_Packet.new(data)
40
+ when SteamPacket::A2A_PING_HEADER
41
+ return A2A_PING_Packet.new
42
+ when SteamPacket::A2A_ACK_HEADER
43
+ return A2A_ACK_Packet.new(data)
44
+ when SteamPacket::A2S_PLAYER_HEADER
45
+ return A2S_PLAYER_Packet.new
46
+ when SteamPacket::S2A_PLAYER_HEADER
47
+ return S2A_PLAYER_Packet.new(data)
48
+ when SteamPacket::A2S_RULES_HEADER
49
+ return A2S_RULES_Packet
50
+ when SteamPacket::S2A_RULES_HEADER
51
+ return S2A_RULES_Packet.new(data)
52
+ when SteamPacket::A2S_SERVERQUERY_GETCHALLENGE_HEADER
53
+ return A2S_SERVERQUERY_GETCHALLENGE_Packet.new
54
+ when SteamPacket::S2C_CHALLENGE_HEADER
55
+ return S2C_CHALLENGE_Packet.new(data)
56
+ when SteamPacket::A2M_GET_SERVERS_BATCH2_HEADER
57
+ return A2M_GET_SERVERS_BATCH2.new(data)
58
+ when SteamPacket::M2A_SERVER_BATCH_HEADER
59
+ return M2A_SERVER_BATCH_Packet.new(data)
60
+ when SteamPacket::RCON_GOLDSRC_CHALLENGE_HEADER,
61
+ SteamPacket::RCON_GOLDSRC_NO_CHALLENGE_HEADER,
62
+ SteamPacket::RCON_GOLDSRC_RESPONSE_HEADER
63
+ return RCONGoldSrcResponse.new(data)
64
+ else
65
+ raise Exception.new("Unknown packet with header 0x#{header.to_s 16} received.")
66
+ end
67
+ end
68
+
69
+ def self.reassemble_packet(split_packets, is_compressed = false, packet_checksum = 0)
70
+ packet_data = split_packets.join ""
71
+
72
+ if is_compressed
73
+ begin
74
+ require 'bz2'
75
+ packet_data = BZ2.uncompress(packet_data)
76
+ rescue LoadError
77
+ raise SteamCondenserException.new("You need to install the libbzip2 interface for Ruby.")
78
+ end
79
+
80
+ if Zlib.crc32(packet_data) != packet_checksum
81
+ raise PacketFormatException.new("CRC32 checksum mismatch of uncompressed packet data.")
82
+ end
83
+ end
84
+
85
+ packet_data = packet_data[4..-1]
86
+
87
+ return self.get_packet_from_data(packet_data)
88
+ end
89
+
90
+ end
@@ -0,0 +1,222 @@
1
+ # This code is free software; you can redistribute it and/or modify it under the
2
+ # terms of the new BSD License.
3
+ #
4
+ # Copyright (c) 2008-2009, Sebastian Staudt
5
+
6
+ require "abstract_class"
7
+ require "exceptions/steam_condenser_exception"
8
+ require "steam/steam_player"
9
+ require "steam/packets/a2s_info_packet"
10
+ require "steam/packets/a2a_ping_packet"
11
+ require "steam/packets/a2s_player_packet"
12
+ require "steam/packets/a2s_rules_packet"
13
+ require "steam/packets/a2s_serverquery_getchallenge_packet"
14
+ require "steam/packets/s2a_info_base_packet"
15
+ require "steam/packets/s2a_player_packet"
16
+ require "steam/packets/s2a_rules_packet"
17
+ require "steam/packets/s2c_challenge_packet"
18
+
19
+ class GameServer
20
+
21
+ include AbstractClass
22
+
23
+ REQUEST_CHALLENGE = 0
24
+ REQUEST_INFO = 1
25
+ REQUEST_PLAYER = 2
26
+ REQUEST_RULES = 3
27
+
28
+ # Returns the last measured response time of this server
29
+ #
30
+ # If there's no data, update_ping is called to measure the current response
31
+ # time of the server.
32
+ #
33
+ # Whenever you want to get a new value for the ping time call update_ping.
34
+ def get_ping
35
+ if @ping == nil
36
+ self.update_ping
37
+ end
38
+ return @ping
39
+ end
40
+
41
+ # Returns an array of the player's currently playing on this server.
42
+ #
43
+ # If there's no data, update_player_info is called to get the current list of
44
+ # players.
45
+ #
46
+ # As the players and their scores change quite often be sure to update this
47
+ # list regularly by calling update_player_info.
48
+ def get_players(rcon_password = nil)
49
+ if @player_hash == nil
50
+ self.update_player_info(rcon_password)
51
+ end
52
+
53
+ return @player_hash
54
+ end
55
+
56
+ # Returns a hash of the settings applied on the server. These settings are
57
+ # also called rules.
58
+ # The hash has the format of +rule_name+ => +rule_value+
59
+ #
60
+ # If there's no data, update_rules_info is called to get the current list of
61
+ # rules.
62
+ #
63
+ # As the rules usually don't change often, there's almost no need to update
64
+ # this hash. But if you need to, you can achieve this by calling
65
+ # update_rules_info.
66
+ def get_rules
67
+ if @rules_hash == nil
68
+ self.update_rules_info
69
+ end
70
+
71
+ return @rules_hash
72
+ end
73
+
74
+ # Returns a hash with basic information on the server.
75
+ #
76
+ # If there's no data, update_server_info is called to get up-to-date
77
+ # information.
78
+ #
79
+ # The server information usually only changes on map change and when players
80
+ # join or leave. As the latter changes can be monitored by calling
81
+ # update_player_info, there's no need to call update_server_info very often.
82
+ def get_server_info
83
+ if @info_hash == nil
84
+ self.update_server_info
85
+ end
86
+
87
+ return @info_hash
88
+ end
89
+
90
+ def handle_response_for_request(request_type, repeat_on_failure = true)
91
+ begin
92
+ case request_type
93
+ when GameServer::REQUEST_CHALLENGE then
94
+ request_packet = A2S_SERVERQUERY_GETCHALLENGE_Packet.new
95
+ expected_response = S2C_CHALLENGE_Packet
96
+ when GameServer::REQUEST_INFO then
97
+ request_packet = A2S_INFO_Packet.new
98
+ expected_response = S2A_INFO_BasePacket
99
+ when GameServer::REQUEST_PLAYER then
100
+ request_packet = A2S_PLAYER_Packet.new(@challenge_number)
101
+ expected_response = S2A_PLAYER_Packet
102
+ when GameServer::REQUEST_RULES then
103
+ request_packet = A2S_RULES_Packet.new(@challenge_number)
104
+ expected_response = S2A_RULES_Packet
105
+ else
106
+ raise SteamCondenserException.new("Called with wrong request type.")
107
+ end
108
+
109
+ self.send_request request_packet
110
+ response_packet = self.get_reply
111
+
112
+ if response_packet.kind_of? S2A_INFO_BasePacket
113
+ @info_hash = response_packet.get_info_hash
114
+ elsif response_packet.kind_of? S2A_PLAYER_Packet
115
+ @player_hash = response_packet.player_hash
116
+ elsif response_packet.kind_of? S2A_RULES_Packet
117
+ @rules_hash = response_packet.get_rules_hash
118
+ elsif response_packet.kind_of? S2C_CHALLENGE_Packet
119
+ @challenge_number = response_packet.get_challenge_number
120
+ else
121
+ raise SteamCondenserException.new("Response of type #{response_packet.class} cannot be handled by this method.")
122
+ end
123
+
124
+ unless response_packet.kind_of? expected_response
125
+ warn "Expected #{expected_response}, got #{response_packet.class}."
126
+ self.handle_response_for_request(request_type, false) if repeat_on_failure
127
+ end
128
+ rescue TimeoutException
129
+ warn "Expected #{expected_response}, but timed out."
130
+ end
131
+ end
132
+
133
+ def init
134
+ self.update_ping
135
+ self.update_server_info
136
+ self.update_challenge_number
137
+ end
138
+
139
+ def update_player_info(rcon_password = nil)
140
+ self.handle_response_for_request GameServer::REQUEST_PLAYER
141
+
142
+ unless rcon_password.nil? or @player_hash.empty?
143
+ rcon_auth(rcon_password)
144
+ players = rcon_exec('status').split("\n")[7..-1]
145
+ if is_a? GoldSrcServer
146
+ players.pop
147
+ end
148
+
149
+ players.each do |player|
150
+ player_data = split_player_status(player)
151
+ @player_hash[player_data[1]].add_info(*player_data)
152
+ end
153
+ end
154
+ end
155
+
156
+ def update_rules_info
157
+ self.handle_response_for_request GameServer::REQUEST_RULES
158
+ end
159
+
160
+ def update_server_info
161
+ self.handle_response_for_request GameServer::REQUEST_INFO
162
+ end
163
+
164
+ def update_challenge_number
165
+ self.handle_response_for_request GameServer::REQUEST_CHALLENGE
166
+ end
167
+
168
+ def update_ping
169
+ self.send_request A2A_PING_Packet.new
170
+ start_time = Time.now
171
+ self.get_reply
172
+ end_time = Time.now
173
+ return @ping = (end_time - start_time) * 1000
174
+ end
175
+
176
+ # Checks whether the listening port number of the server is in a valid range
177
+ def initialize(port_number = 27015)
178
+ unless port_number.to_i > 0 and port_number.to_i < 65536
179
+ raise ArgumentError.new("The listening port of the server has to be a number greater than 0 and less than 65536.")
180
+ end
181
+ end
182
+
183
+ def to_s
184
+ return_string = ""
185
+
186
+ return_string << "Ping: #{@ping}\n"
187
+ return_string << "Challenge number: #{@challenge_number}\n"
188
+
189
+ if @info_hash != nil
190
+ return_string << "Info:\n"
191
+ @info_hash.each do |key, value|
192
+ return_string << " #{key}: #{value.inspect}\n"
193
+ end
194
+ end
195
+
196
+ if @player_hash != nil
197
+ return_string << "Players:\n"
198
+ @player_hash.each do |player|
199
+ return_string << " #{player}\n"
200
+ end
201
+ end
202
+
203
+ if @rules_hash != nil
204
+ return_string << "Rules:\n"
205
+ @rules_hash.each do |key, value|
206
+ return_string << " #{key}: #{value}\n"
207
+ end
208
+ end
209
+
210
+ return return_string
211
+ end
212
+
213
+ protected
214
+
215
+ def get_reply
216
+ @socket.get_reply
217
+ end
218
+
219
+ def send_request packet
220
+ @socket.send packet
221
+ end
222
+ end