Module: SteamCondenser::Servers::GameServer

Includes:
BaseServer
Included in:
GoldSrcServer, SourceServer
Defined in:
lib/steam-condenser/servers/game_server.rb

Overview

This module is included by classes representing different game server implementations and provides the basic functionality to communicate with them using the common query protocol

Author:

  • Sebastian Staudt

Instance Attribute Summary

Attributes included from BaseServer

#host_names, #ip_addresses

Class Method Summary collapse

Instance Method Summary collapse

Methods included from BaseServer

#disconnect, #failsafe, #init_socket, #rotate_ip

Class Method Details

.player_status_attributes(status_header) ⇒ Array<Symbol>

Parses the player attribute names supplied by rcon status


37
38
39
40
41
42
43
44
45
46
47
48
# File 'lib/steam-condenser/servers/game_server.rb', line 37

def self.player_status_attributes(status_header)
  status_header.split.map do |attribute|
    case attribute
      when 'connected'
        :time
      when 'frag'
        :score
      else
        attribute.to_sym
    end
  end
end

.split_player_status(attributes, player_status) ⇒ Hash<Symbol, String>

Splits the player status obtained with rcon status


57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
# File 'lib/steam-condenser/servers/game_server.rb', line 57

def self.split_player_status(attributes, player_status)
  player_status.sub! /^\d+ +/, '' if attributes.first != :userid

  first_quote = player_status.index '"'
  last_quote  = player_status.rindex '"'
  data = [
    player_status[0, first_quote],
    player_status[first_quote + 1..last_quote - 1],
    player_status[last_quote + 1..-1]
  ]
  data = [ data[0].split, data[1], data[2].split ]
  data.flatten!

  if attributes.size > data.size && attributes.include?(:state)
    data.insert 3, nil, nil, nil
  elsif attributes.size < data.size
    data.delete_at 1
  end

  player_data = {}
  data.each_index do |i|
    player_data[attributes[i]] = data[i]
  end

  player_data
end

Instance Method Details

#handle_response_for_request(request_type, repeat_on_failure = true) ⇒ Object

Sends the specified request to the server and handles the returned response

Depending on the given request type this will fill the various data attributes of the server object.

Raises:

  • (Error)

    if either the request type or the response packet is not known


207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
# File 'lib/steam-condenser/servers/game_server.rb', line 207

def handle_response_for_request(request_type, repeat_on_failure = true)
  case request_type
    when :challenge then
      request_packet = Packets::A2S_PLAYER_Packet.new
      expected_response = Packets::S2C_CHALLENGE_Packet
    when :info then
      request_packet = Packets::A2S_INFO_Packet.new
      expected_response = Packets::S2A_INFO_BasePacket
    when :players then
      request_packet = Packets::A2S_PLAYER_Packet.new(@challenge_number)
      expected_response = Packets::S2A_PLAYER_Packet
    when :rules then
      request_packet = Packets::A2S_RULES_Packet.new(@challenge_number)
      expected_response = Packets::S2A_RULES_Packet
    else
      raise SteamCondenser::Error, 'Called with wrong request type.'
  end

  send_request request_packet
  response_packet = reply

  if response_packet.kind_of? Packets::S2A_INFO_BasePacket
    @info_hash = response_packet.info
  elsif response_packet.kind_of? Packets::S2A_PLAYER_Packet
    @player_hash = response_packet.player_hash
  elsif response_packet.kind_of? Packets::S2A_RULES_Packet
    @rules_hash = response_packet.rules_hash
  elsif response_packet.kind_of? Packets::S2C_CHALLENGE_Packet
    @challenge_number = response_packet.challenge_number
  else
    raise SteamCondenser::Error, "Response of type #{response_packet.class} cannot be handled by this method."
  end

  unless response_packet.kind_of? expected_response
    if log.info?
      expected_class = expected_response.class.name[/[^:]*\z/]
      response_class = response_packet.class.name[/[^:]*\z/]
      log.info "Expected #{expected_class}, got #{response_class}."
    end
    handle_response_for_request(request_type, false) if repeat_on_failure
  end
end

#initObject

Initializes this server object with basic information


255
256
257
258
259
# File 'lib/steam-condenser/servers/game_server.rb', line 255

def init
  update_ping
  update_server_info
  update_challenge_number
end

#initialize(address, port = 27015) ⇒ Object

Creates a new instance of a game server object

Raises:

  • (Error)

    if an host name cannot be resolved


91
92
93
94
95
# File 'lib/steam-condenser/servers/game_server.rb', line 91

def initialize(address, port = 27015)
  super

  @rcon_authenticated = false
end

#pingFixnum

Returns the last measured response time of this server

If the latency hasn't been measured yet, it is done when calling this method for the first time.

If this information is vital to you, be sure to call #update_ping regularly to stay up-to-date.

See Also:


107
108
109
110
# File 'lib/steam-condenser/servers/game_server.rb', line 107

def ping
  update_ping if @ping.nil?
  @ping
end

#players(rcon_password = nil) ⇒ Hash

Returns a list of players currently playing on this server

If the players haven't been fetched yet, it is done when calling this method for the first time.

As the players and their scores change quite often be sure to update this list regularly by calling #update_players if you rely on this information.

See Also:


126
127
128
129
# File 'lib/steam-condenser/servers/game_server.rb', line 126

def players(rcon_password = nil)
  update_players(rcon_password) if @player_hash.nil?
  @player_hash
end

#rcon_auth(password) ⇒ Boolean

This method is abstract.

Must be be implemented by including classes to handle the authentication

Authenticates the connection for RCON communication with the server

Raises:

  • (NotImplementedError)

See Also:


138
139
140
# File 'lib/steam-condenser/servers/game_server.rb', line 138

def rcon_auth(password)
  raise NotImplementedError
end

#rcon_authenticated?Boolean

Returns whether the RCON connection to this server is already authenticated

See Also:


146
147
148
# File 'lib/steam-condenser/servers/game_server.rb', line 146

def rcon_authenticated?
  @rcon_authenticated
end

#rcon_exec(command) ⇒ String

This method is abstract.

Must be be implemented by including classes to handle the command execution

Remotely executes a command on the server via RCON

Raises:

  • (NotImplementedError)

See Also:


157
158
159
# File 'lib/steam-condenser/servers/game_server.rb', line 157

def rcon_exec(command)
  raise NotImplementedError
end

#replyPackets::BasePacket (protected)

Receives a response from the server


390
391
392
# File 'lib/steam-condenser/servers/game_server.rb', line 390

def reply
  @socket.reply
end

#rulesHash<String, String>

Returns the settings applied on the server. These settings are also called rules.

If the rules haven't been fetched yet, it is done when calling this method for the first time.

As the rules usually don't change often, there's almost no need to update this hash. But if you need to, you can achieve this by calling #update_rules.

See Also:


173
174
175
176
# File 'lib/steam-condenser/servers/game_server.rb', line 173

def rules
  update_rules if @rules_hash.nil?
  @rules_hash
end

#send_request(packet) ⇒ Object (protected)

Sends a request packet to the server


398
399
400
# File 'lib/steam-condenser/servers/game_server.rb', line 398

def send_request(packet)
  @socket.send packet
end

#server_infoHash

Returns a hash with basic information on the server.

If the server information haven't been fetched yet, it is done when calling this method for the first time.

The server information usually only changes on map change and when players join or leave. As the latter changes can be monitored by calling #update_players, there's no need to call #update_server_info very often.


190
191
192
193
# File 'lib/steam-condenser/servers/game_server.rb', line 190

def server_info
  update_server_info if @info_hash.nil?
  @info_hash
end

#to_sString

Returns a human-readable text representation of the server


355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
# File 'lib/steam-condenser/servers/game_server.rb', line 355

def to_s
  return_string = ''

  return_string << "Ping: #@ping\n"
  return_string << "Challenge number: #@challenge_number\n"

  unless @info_hash.nil?
    return_string << "Info:\n"
    @info_hash.each do |key, value|
      return_string << "  #{key}: #{value.inspect}\n"
    end
  end

  unless @player_hash.nil?
    return_string << "Players:\n"
    @player_hash.each_value do |player|
      return_string << "  #{player}\n"
    end
  end

  unless @rules_hash.nil?
    return_string << "Rules:\n"
    @rules_hash.each do |key, value|
      return_string << "  #{key}: #{value}\n"
    end
  end

  return_string
end

#update_challenge_numberObject

Sends a A2S_SERVERQUERY_GETCHALLENGE request to the server and updates the challenge number used to communicate with this server

There's usually no need to call this method explicitly, because #handle_response_for_request will automatically get the challenge number when the server assigns a new one.


331
332
333
# File 'lib/steam-condenser/servers/game_server.rb', line 331

def update_challenge_number
  handle_response_for_request :challenge
end

#update_pingFixnum

Sends a A2S_INFO request to the server and measures the time needed for the reply

If this information is vital to you, be sure to call this method regularly to stay up-to-date.

See Also:


343
344
345
346
347
348
349
# File 'lib/steam-condenser/servers/game_server.rb', line 343

def update_ping
  send_request Packets::A2S_INFO_Packet.new
  start_time = Time.now
  reply
  end_time = Time.now
  @ping = (end_time - start_time) * 1000
end

#update_players(rcon_password = nil) ⇒ Object

Sends a A2S_PLAYERS request to the server and updates the players' data for this server

As the players and their scores change quite often be sure to update this list regularly by calling this method if you rely on this information.


273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
# File 'lib/steam-condenser/servers/game_server.rb', line 273

def update_players(rcon_password = nil)
  handle_response_for_request :players

  unless @rcon_authenticated
    return if rcon_password.nil?
    rcon_auth rcon_password
  end

  players = rcon_exec('status').lines.select do |line|
    line.start_with?('#') && line != "#end\n"
  end.map do |line|
    line[1..-1].strip
  end
  attributes = GameServer.player_status_attributes players.shift

  players.each do |player|
    player_data = GameServer.split_player_status(attributes, player)
    player_name = player_data[:name]
    if @player_hash.key? player_name
      @player_hash[player_name].add_info player_data
    end
  end
end

#update_rulesObject

Sends a A2S_RULES request to the server and updates the rules of this server

As the rules usually don't change often, there's almost no need to update this hash. But if you need to, you can achieve this by calling this method.


305
306
307
# File 'lib/steam-condenser/servers/game_server.rb', line 305

def update_rules
  handle_response_for_request :rules
end

#update_server_infoObject

Sends a A2S_INFO request to the server and updates this server's basic information

The server information usually only changes on map change and when players join or leave. As the latter changes can be monitored by calling #update_players, there's no need to call this method very often.


318
319
320
# File 'lib/steam-condenser/servers/game_server.rb', line 318

def update_server_info
  handle_response_for_request :info
end