steam-condenser 0.10.1 → 0.11.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 (73) hide show
  1. data/VERSION.yml +2 -2
  2. data/lib/datagram_channel.rb +30 -33
  3. data/lib/exceptions/packet_format_exception.rb +7 -3
  4. data/lib/exceptions/rcon_ban_exception.rb +7 -5
  5. data/lib/exceptions/rcon_no_auth_exception.rb +8 -6
  6. data/lib/exceptions/timeout_exception.rb +10 -4
  7. data/lib/socket_channel.rb +11 -17
  8. data/lib/steam/community/cacheable.rb +5 -7
  9. data/lib/steam/community/css/css_map.rb +31 -0
  10. data/lib/steam/community/css/css_stats.rb +116 -0
  11. data/lib/steam/community/css/css_weapon.rb +33 -0
  12. data/lib/steam/community/dods/dods_stats.rb +3 -3
  13. data/lib/steam/community/game_stats.rb +14 -13
  14. data/lib/steam/community/game_weapon.rb +3 -5
  15. data/lib/steam/community/l4d/abstract_l4d_stats.rb +4 -2
  16. data/lib/steam/community/l4d/l4d_explosive.rb +2 -2
  17. data/lib/steam/community/l4d/l4d_map.rb +3 -3
  18. data/lib/steam/community/steam_group.rb +2 -4
  19. data/lib/steam/community/steam_id.rb +2 -2
  20. data/lib/steam/community/tf2/tf2_class.rb +17 -17
  21. data/lib/steam/community/tf2/tf2_class_factory.rb +17 -17
  22. data/lib/steam/community/tf2/tf2_engineer.rb +9 -9
  23. data/lib/steam/community/tf2/tf2_medic.rb +9 -9
  24. data/lib/steam/community/tf2/tf2_sniper.rb +7 -7
  25. data/lib/steam/community/tf2/tf2_spy.rb +9 -9
  26. data/lib/steam/community/tf2/tf2_stats.rb +9 -9
  27. data/lib/steam/packets/a2a_ack_packet.rb +11 -9
  28. data/lib/steam/packets/a2a_ping_packet.rb +5 -4
  29. data/lib/steam/packets/a2m_get_servers_batch2_packet.rb +13 -12
  30. data/lib/steam/packets/a2s_info_packet.rb +8 -7
  31. data/lib/steam/packets/a2s_player_packet.rb +6 -6
  32. data/lib/steam/packets/a2s_rules_packet.rb +5 -5
  33. data/lib/steam/packets/a2s_serverquery_getchallenge_packet.rb +4 -3
  34. data/lib/steam/packets/m2a_server_batch_packet.rb +20 -22
  35. data/lib/steam/packets/rcon/rcon_auth_request.rb +5 -5
  36. data/lib/steam/packets/rcon/rcon_auth_response.rb +6 -6
  37. data/lib/steam/packets/rcon/rcon_exec_request.rb +5 -5
  38. data/lib/steam/packets/rcon/rcon_exec_response.rb +2 -2
  39. data/lib/steam/packets/rcon/rcon_goldsrc_request.rb +7 -7
  40. data/lib/steam/packets/rcon/rcon_goldsrc_response.rb +8 -8
  41. data/lib/steam/packets/rcon/rcon_packet.rb +12 -16
  42. data/lib/steam/packets/rcon/rcon_packet_factory.rb +19 -19
  43. data/lib/steam/packets/request_with_challenge.rb +5 -5
  44. data/lib/steam/packets/s2a_info2_packet.rb +28 -30
  45. data/lib/steam/packets/s2a_info_base_packet.rb +11 -16
  46. data/lib/steam/packets/s2a_info_detailed_packet.rb +30 -28
  47. data/lib/steam/packets/s2a_player_packet.rb +7 -9
  48. data/lib/steam/packets/s2a_rules_packet.rb +8 -12
  49. data/lib/steam/packets/s2c_challenge_packet.rb +7 -6
  50. data/lib/steam/packets/steam_packet.rb +7 -6
  51. data/lib/steam/packets/steam_packet_factory.rb +37 -38
  52. data/lib/steam/servers/game_server.rb +76 -86
  53. data/lib/steam/servers/goldsrc_server.rb +5 -5
  54. data/lib/steam/servers/master_server.rb +24 -24
  55. data/lib/steam/servers/source_server.rb +17 -19
  56. data/lib/steam/sockets/goldsrc_socket.rb +43 -46
  57. data/lib/steam/sockets/master_server_socket.rb +11 -13
  58. data/lib/steam/sockets/rcon_socket.rb +15 -17
  59. data/lib/steam/sockets/source_socket.rb +21 -21
  60. data/lib/steam/sockets/steam_socket.rb +25 -28
  61. data/lib/steam/steam_player.rb +3 -1
  62. data/lib/stringio_additions.rb +48 -0
  63. data/test/datagram_channel_tests.rb +8 -8
  64. data/test/query_tests.rb +15 -14
  65. data/test/rcon_tests.rb +32 -31
  66. data/test/socket_channel_tests.rb +7 -7
  67. data/test/steam/communtiy/steam_community_test_suite.rb +5 -2
  68. data/test/steam_community_tests.rb +4 -3
  69. data/test/stringio_additions_tests.rb +77 -0
  70. metadata +15 -8
  71. data/lib/byte_buffer.rb +0 -126
  72. data/lib/exceptions/buffer_underflow_exception.rb +0 -8
  73. data/test/byte_buffer_tests.rb +0 -76
data/VERSION.yml CHANGED
@@ -1,4 +1,4 @@
1
1
  ---
2
2
  :major: 0
3
- :minor: 10
4
- :patch: 1
3
+ :minor: 11
4
+ :patch: 0
@@ -1,71 +1,68 @@
1
1
  # This code is free software; you can redistribute it and/or modify it under the
2
2
  # terms of the new BSD License.
3
3
  #
4
- # Copyright (c) 2008-2009, Sebastian Staudt
4
+ # Copyright (c) 2008-2010, Sebastian Staudt
5
5
 
6
6
  require 'ipaddr'
7
7
  require 'socket'
8
8
 
9
- require 'byte_buffer'
9
+ require 'stringio_additions'
10
10
 
11
11
  class DatagramChannel
12
-
12
+
13
13
  attr_reader :socket
14
-
14
+
15
15
  def self.open
16
- return DatagramChannel.new
16
+ DatagramChannel.new
17
17
  end
18
-
18
+
19
19
  def close
20
20
  @socket.close
21
21
  end
22
-
22
+
23
23
  def connect(*args)
24
24
  if args[0].is_a? IPAddr
25
25
  ip_address = args[0].to_s
26
26
  elsif args[0].is_a? String
27
27
  sockaddr = (Socket.gethostbyname args[0])[3]
28
28
  ip_address = "#{sockaddr[0]}.#{sockaddr[1]}.#{sockaddr[2]}.#{sockaddr[3]}"
29
+ else
30
+ raise ArgumentError
29
31
  end
30
-
32
+
31
33
  if args[1].is_a? Numeric
32
34
  port_number = args[1]
33
35
  else
34
36
  port_number = args[1].to_i
35
37
  end
36
-
38
+
37
39
  @socket.connect ip_address, port_number
38
-
39
- return self
40
+
41
+ self
40
42
  end
41
-
43
+
42
44
  def configure_blocking(do_block)
43
45
  end
44
-
45
- def read(destination_buffer)
46
- if !destination_buffer.is_a? ByteBuffer
47
- raise ArgumentError
48
- end
49
-
50
- length = destination_buffer.remaining
51
- data = @socket.recv length
52
- destination_buffer.put data
53
-
54
- return data.length
46
+
47
+ def read(destination_io)
48
+ raise ArgumentError unless destination_io.is_a? StringIO
49
+
50
+ data = @socket.recv destination_io.remaining
51
+ length = destination_io.write data
52
+ destination_io.truncate length
55
53
  end
56
-
57
- def write(source_buffer)
58
- if !source_buffer.is_a? ByteBuffer
59
- raise ArgumentError
60
- end
61
-
62
- return @socket.send(source_buffer.get, 0)
54
+
55
+ def write(source_io)
56
+ raise ArgumentError unless source_io.is_a? StringIO
57
+
58
+ @socket.send(source_io.get, 0)
63
59
  end
64
-
60
+
65
61
  protected
66
-
62
+
67
63
  def initialize
68
64
  @socket = UDPSocket.new
69
- self.configure_blocking true
65
+ configure_blocking true
70
66
  end
67
+
71
68
  end
@@ -1,10 +1,14 @@
1
1
  # This code is free software; you can redistribute it and/or modify it under the
2
2
  # terms of the new BSD License.
3
3
  #
4
- # Copyright (c) 2008-2009, Sebastian Staudt
4
+ # Copyright (c) 2008-2010, Sebastian Staudt
5
5
 
6
- require "exceptions/steam_condenser_exception"
6
+ require 'exceptions/steam_condenser_exception'
7
7
 
8
8
  class PacketFormatException < SteamCondenserException
9
9
 
10
- end
10
+ def initialize
11
+ super 'The packet data received doesn\'t match the packet format.'
12
+ end
13
+
14
+ end
@@ -1,12 +1,14 @@
1
1
  # This code is free software; you can redistribute it and/or modify it under the
2
2
  # terms of the new BSD License.
3
3
  #
4
- # Copyright (c) 2009, Sebastian Staudt
4
+ # Copyright (c) 2009-2010, Sebastian Staudt
5
+
6
+ require 'exceptions/steam_condenser_exception'
7
+
8
+ class RCONBanException < SteamCondenserException
5
9
 
6
- class RCONBanException < Exception
7
-
8
10
  def initialize
9
11
  super 'You have been banned from this server.'
10
12
  end
11
-
12
- end
13
+
14
+ end
@@ -1,12 +1,14 @@
1
1
  # This code is free software; you can redistribute it and/or modify it under the
2
2
  # terms of the new BSD License.
3
3
  #
4
- # Copyright (c) 2008-2009, Sebastian Staudt
4
+ # Copyright (c) 2008-2010, Sebastian Staudt
5
+
6
+ require 'exceptions/steam_condenser_exception'
7
+
8
+ class RCONNoAuthException < SteamCondenserException
5
9
 
6
- class RCONNoAuthException < Exception
7
-
8
10
  def initialize
9
- super "Not authenticated yet."
11
+ super 'Not authenticated yet.'
10
12
  end
11
-
12
- end
13
+
14
+ end
@@ -1,8 +1,14 @@
1
1
  # This code is free software; you can redistribute it and/or modify it under the
2
2
  # terms of the new BSD License.
3
3
  #
4
- # Copyright (c) 2008-2009, Sebastian Staudt
4
+ # Copyright (c) 2008-2010, Sebastian Staudt
5
5
 
6
- class TimeoutException < StandardError
7
-
8
- end
6
+ require 'exceptions/steam_condenser_exception'
7
+
8
+ class TimeoutException < SteamCondenserException
9
+
10
+ def initialize
11
+ super 'The operation timed out.'
12
+ end
13
+
14
+ end
@@ -1,20 +1,20 @@
1
1
  # This code is free software; you can redistribute it and/or modify it under the
2
2
  # terms of the new BSD License.
3
3
  #
4
- # Copyright (c) 2008-2009, Sebastian Staudt
4
+ # Copyright (c) 2008-2010, Sebastian Staudt
5
5
 
6
6
  require 'ipaddr'
7
7
  require 'socket'
8
8
  require 'timeout'
9
9
 
10
- require 'byte_buffer'
10
+ require 'stringio_additions'
11
11
 
12
12
  class SocketChannel
13
13
 
14
14
  attr_reader :socket
15
15
 
16
16
  def self.open
17
- return SocketChannel.new
17
+ SocketChannel.new
18
18
  end
19
19
 
20
20
  def close
@@ -27,7 +27,7 @@ class SocketChannel
27
27
  @connected = true
28
28
  end
29
29
 
30
- return self
30
+ self
31
31
  end
32
32
 
33
33
  def initialize
@@ -35,27 +35,21 @@ class SocketChannel
35
35
  end
36
36
 
37
37
  def connected?
38
- return @connected
38
+ @connected
39
39
  end
40
40
 
41
41
  def read(destination_buffer)
42
- if !destination_buffer.is_a? ByteBuffer
43
- raise ArgumentError
44
- end
45
-
46
- length = destination_buffer.remaining
47
- data = @socket.recv length
48
- destination_buffer.put data
42
+ raise ArgumentError unless destination_buffer.is_a? StringIO
49
43
 
50
- return data.length
44
+ data = @socket.recv destination_buffer.remaining
45
+ length = destination_buffer.write data
46
+ destination_buffer.truncate length
51
47
  end
52
48
 
53
49
  def write(source_buffer)
54
- if !source_buffer.is_a? ByteBuffer
55
- raise ArgumentError
56
- end
50
+ raise ArgumentError unless source_buffer.is_a? StringIO
57
51
 
58
- return @socket.send(source_buffer.get, 0)
52
+ @socket.send(source_buffer.get, 0)
59
53
  end
60
54
 
61
55
  end
@@ -30,9 +30,7 @@ module Cacheable
30
30
 
31
31
  # Returns whether the requested object +id+ is already cached
32
32
  def cached?(id)
33
- if id.is_a? String
34
- id.downcase!
35
- end
33
+ id.downcase! if id.is_a? String
36
34
  class_variable_get(:@@cache).key?(id)
37
35
  end
38
36
 
@@ -59,11 +57,11 @@ module Cacheable
59
57
  attr_reader :fetch_time
60
58
 
61
59
  # Creates a new object for the given +id+, either numeric or
62
- # the custom URL specified by the user. If +fetch+ is +true+ (default),
60
+ # the custom URL specified by the user. If +fetch_now+ is +true+ (default),
63
61
  # fetch is used to load data into the object.
64
62
  # This method is overridden by Cacheable::ClassMethods#new.
65
- def initialize(fetch = true) #:notnew:
66
- self.fetch if fetch
63
+ def initialize(fetch_now = true) #:notnew:
64
+ fetch if fetch_now
67
65
  cache
68
66
  end
69
67
 
@@ -91,5 +89,5 @@ module Cacheable
91
89
  def fetched?
92
90
  !@fetch_time.nil?
93
91
  end
94
-
92
+
95
93
  end
@@ -0,0 +1,31 @@
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) 2010, Sebastian Staudt
5
+
6
+ # CSSMap holds statistical information about maps played by a player in
7
+ # Counter-Strike: Source.
8
+ class CSSMap
9
+
10
+ attr_reader :name, :rounds_lost, :rounds_played, :rounds_won,
11
+ :rounds_won_percentage
12
+
13
+ # Creates a new instance of CSSMap based on the assigned map name and XML
14
+ # data
15
+ def initialize(map_name, maps_data)
16
+ @name = map_name
17
+
18
+ @favorite = (maps_data.elements['favorite'].text == @name)
19
+ @rounds_played = maps_data.elements["#{@name}_rounds"].text.to_i
20
+ @rounds_won = maps_data.elements["#{@name}_wins"].text.to_i
21
+
22
+ @rounds_lost = @rounds_played - @rounds_won
23
+ @rounds_won_percentage = (@rounds_played > 0) ? @rounds_won.to_f / @rounds_played : 0
24
+ end
25
+
26
+ # Returns whether this map is the favorite map of this player
27
+ def favorite?
28
+ @favorite
29
+ end
30
+
31
+ end
@@ -0,0 +1,116 @@
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) 2010, Sebastian Staudt
5
+
6
+ require 'steam/community/css/css_map'
7
+ require 'steam/community/css/css_weapon'
8
+
9
+ # The CSSStats class represents the game statistics for a single user in
10
+ # Counter-Strike: Source
11
+ class CSSStats < GameStats
12
+
13
+ MAPS = [ 'cs_assault', 'cs_compound', 'cs_havana', 'cs_italy', 'cs_militia',
14
+ 'cs_office', 'de_aztec', 'de_cbble', 'de_chateau', 'de_dust',
15
+ 'de_dust2', 'de_inferno', 'de_nuke', 'de_piranesi', 'de_port',
16
+ 'de_prodigy', 'de_tides', 'de_train' ]
17
+
18
+ WEAPONS = [ 'deagle', 'usp', 'glock', 'p228', 'elite', 'fiveseven', 'awp',
19
+ 'ak47', 'm4a1', 'aug', 'sg552', 'sg550', 'galil', 'famas',
20
+ 'scout', 'g3sg1', 'p90', 'mp5navy', 'tmp', 'mac10', 'ump45',
21
+ 'm3', 'xm1014', 'm249', 'knife', 'grenade' ]
22
+
23
+ attr_reader :last_match_stats, :total_stats
24
+
25
+ # Creates a CSSStats object by calling the super constructor with the game
26
+ # name "cs:s"
27
+ def initialize(steam_id)
28
+ super steam_id, 'cs:s'
29
+
30
+ if public?
31
+ @last_match_stats = {}
32
+ @total_stats = {}
33
+
34
+ @last_match_stats[:cost_per_kill] = @xml_data.elements['stats/lastmatch/costkill'].text.to_f
35
+ @last_match_stats[:ct_wins] = @xml_data.elements['stats/lastmatch/ct_wins'].text.to_i
36
+ @last_match_stats[:damage] = @xml_data.elements['stats/lastmatch/dmg'].text.to_i
37
+ @last_match_stats[:deaths] = @xml_data.elements['stats/lastmatch/deaths'].text.to_i
38
+ @last_match_stats[:dominations] = @xml_data.elements['stats/lastmatch/dominations'].text.to_i
39
+ @last_match_stats[:favorite_weapon] = @xml_data.elements['stats/lastmatch/favwpn'].text
40
+ @last_match_stats[:kills] = @xml_data.elements['stats/lastmatch/kills'].text.to_i
41
+ @last_match_stats[:max_players] = @xml_data.elements['stats/lastmatch/max_players'].text.to_i
42
+ @last_match_stats[:money] = @xml_data.elements['stats/lastmatch/money'].text.to_i
43
+ @last_match_stats[:revenges] = @xml_data.elements['stats/lastmatch/revenges'].text.to_i
44
+ @last_match_stats[:stars] = @xml_data.elements['stats/lastmatch/stars'].text.to_i
45
+ @last_match_stats[:t_wins] = @xml_data.elements['stats/lastmatch/t_wins'].text.to_i
46
+ @last_match_stats[:wins] = @xml_data.elements['stats/lastmatch/wins'].text.to_i
47
+ @total_stats[:blind_kills] = @xml_data.elements['stats/lifetime/blindkills'].text.to_i
48
+ @total_stats[:bombs_defused] = @xml_data.elements['stats/lifetime/bombsdefused'].text.to_i
49
+ @total_stats[:bombs_planted] = @xml_data.elements['stats/lifetime/bombsplanted'].text.to_i
50
+ @total_stats[:damage] = @xml_data.elements['stats/lifetime/dmg'].text.to_i
51
+ @total_stats[:deaths] = @xml_data.elements['stats/summary/deaths'].text.to_i
52
+ @total_stats[:domination_overkills] = @xml_data.elements['stats/lifetime/dominationoverkills'].text.to_i
53
+ @total_stats[:dominations] = @xml_data.elements['stats/lifetime/dominations'].text.to_i
54
+ @total_stats[:earned_money] = @xml_data.elements['stats/lifetime/money'].text.to_i
55
+ @total_stats[:enemy_weapon_kills] = @xml_data.elements['stats/lifetime/enemywpnkills'].text.to_i
56
+ @total_stats[:headshots] = @xml_data.elements['stats/lifetime/headshots'].text.to_i
57
+ @total_stats[:hits] = @xml_data.elements['stats/summary/shotshit'].text.to_i
58
+ @total_stats[:hostages_rescued] = @xml_data.elements['stats/lifetime/hostagesrescued'].text.to_i
59
+ @total_stats[:kills] = @xml_data.elements['stats/summary/kills'].text.to_i
60
+ @total_stats[:knife_kills] = @xml_data.elements['stats/lifetime/knifekills'].text.to_i
61
+ @total_stats[:logos_sprayed] = @xml_data.elements['stats/lifetime/decals'].text.to_i
62
+ @total_stats[:nightvision_damage] = @xml_data.elements['stats/lifetime/nvgdmg'].text.to_i
63
+ @total_stats[:pistol_rounds_won] = @xml_data.elements['stats/lifetime/pistolrounds'].text.to_i
64
+ @total_stats[:revenges] = @xml_data.elements['stats/lifetime/revenges'].text.to_i
65
+ @total_stats[:rounds_played] = @xml_data.elements['stats/summary/rounds'].text.to_i
66
+ @total_stats[:rounds_won] = @xml_data.elements['stats/summary/wins'].text.to_i
67
+ @total_stats[:seconds_played] = @xml_data.elements['stats/summary/timeplayed'].text.to_i
68
+ @total_stats[:shots] = @xml_data.elements['stats/summary/shots'].text.to_i
69
+ @total_stats[:stars] = @xml_data.elements['stats/summary/stars'].text.to_i
70
+ @total_stats[:weapons_donated] = @xml_data.elements['stats/lifetime/wpndonated'].text.to_i
71
+ @total_stats[:windows_broken] = @xml_data.elements['stats/lifetime/winbroken'].text.to_i
72
+ @total_stats[:zoomed_sniper_kills] = @xml_data.elements['stats/lifetime/zsniperkills'].text.to_i
73
+
74
+ @last_match_stats[:kdratio] = @last_match_stats[:kills].to_f / @last_match_stats[:deaths]
75
+ @total_stats[:accuracy] = @total_stats[:hits].to_f / @total_stats[:shots]
76
+ @total_stats[:kdratio] = @total_stats[:kills].to_f / @total_stats[:deaths]
77
+ @total_stats[:rounds_lost] = @total_stats[:rounds_played] - @total_stats[:rounds_won]
78
+ end
79
+ end
80
+
81
+ # Returns a Hash of CSSMap for this user containing all Counter-Strike:
82
+ # Source maps. If the maps haven't been parsed already, parsing is done now.
83
+ def map_stats
84
+ return unless public?
85
+
86
+ if @map_stats.nil?
87
+ @map_stats = {}
88
+ maps_data = @xml_data.elements['stats/maps']
89
+
90
+ MAPS.each do |map_name|
91
+ @map_stats[map_name] = CSSMap.new(map_name, maps_data)
92
+ end
93
+ end
94
+
95
+ @map_stats
96
+ end
97
+
98
+ # Returns a Hash of CSSWeapon for this user containing all Counter-Strike:
99
+ # Source weapons. If the weapons haven't been parsed already, parsing is done
100
+ # now.
101
+ def weapon_stats
102
+ return unless public?
103
+
104
+ if @weapon_stats.nil?
105
+ @weapon_stats = {}
106
+ weapons_data = @xml_data.elements['stats/weapons']
107
+
108
+ WEAPONS.each do |weapon_name|
109
+ @weapon_stats[weapon_name] = CSSWeapon.new(weapon_name, weapons_data)
110
+ end
111
+ end
112
+
113
+ @weapon_stats
114
+ end
115
+
116
+ 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) 2010, Sebastian Staudt
5
+
6
+ # CSSWeapon holds statistical information about weapons used by a player in
7
+ # Counter-Strike: Source.
8
+ class CSSWeapon
9
+
10
+ attr_reader :accuracy, :hits, :name, :kills, :ksratio, :shots
11
+
12
+ # Creates a new instance of CSSWeapon based on the assigned weapon name and
13
+ # XML data
14
+ def initialize(weapon_name, weapons_data)
15
+ @name = weapon_name
16
+
17
+ @favorite = (weapons_data.elements['favorite'].text == @name)
18
+ @kills = weapons_data.elements["#{@name}_kills"].text.to_i
19
+
20
+ if @name != 'grenade' && @name != 'knife'
21
+ @hits = weapons_data.elements["#{@name}_hits"].text.to_i
22
+ @shots = weapons_data.elements["#{@name}_shots"].text.to_i
23
+ @accuracy = (@shots > 0) ? @hits.to_f / @shots : 0
24
+ @ksratio = (@shots > 0) ? @kills.to_f / @shots : 0
25
+ end
26
+ end
27
+
28
+ # Returns whether this weapon is the favorite weapon of this player
29
+ def favorite?
30
+ @favorite
31
+ end
32
+
33
+ end