steam-condenser 0.8.0 → 0.9.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.
- data/Rakefile +10 -7
- data/VERSION.yml +4 -0
- data/lib/byte_buffer.rb +28 -28
- data/lib/datagram_channel.rb +5 -4
- data/lib/exceptions/rcon_ban_exception.rb +12 -0
- data/lib/socket_channel.rb +21 -14
- data/lib/steam-condenser.rb +11 -0
- data/lib/steam/community/cacheable.rb +95 -0
- data/lib/steam/community/defense_grid/defense_grid_stats.rb +124 -0
- data/lib/steam/community/game_stats.rb +44 -26
- data/lib/steam/community/l4d/l4d_map.rb +3 -3
- data/lib/steam/community/steam_group.rb +77 -6
- data/lib/steam/community/steam_id.rb +127 -101
- data/lib/steam/community/tf2/tf2_stats.rb +5 -5
- data/lib/steam/packets/rcon/rcon_exec_response.rb +6 -8
- data/lib/steam/packets/s2a_rules_packet.rb +15 -8
- data/lib/steam/servers/game_server.rb +2 -4
- data/lib/steam/servers/goldsrc_server.rb +5 -5
- data/lib/steam/servers/source_server.rb +11 -11
- data/lib/steam/sockets/rcon_socket.rb +18 -17
- data/lib/steam/sockets/steam_socket.rb +5 -8
- data/lib/steam/steam_player.rb +5 -5
- data/test/byte_buffer_tests.rb +76 -0
- data/test/datagram_channel_tests.rb +42 -0
- data/test/query_tests.rb +1 -3
- data/test/rcon_tests.rb +1 -3
- data/test/socket_channel_tests.rb +43 -0
- data/test/steam/communtiy/steam_community_test_suite.rb +9 -0
- data/test/steam/communtiy/steam_group_tests.rb +43 -0
- data/test/steam/communtiy/steam_id_tests.rb +48 -0
- data/test/steam_community_tests.rb +17 -9
- metadata +27 -10
data/Rakefile
CHANGED
@@ -13,15 +13,18 @@ test_files = Dir.glob(File.join("test", "**", "*.rb"))
|
|
13
13
|
|
14
14
|
# Gem specification
|
15
15
|
Jeweler::Tasks.new do |s|
|
16
|
-
s.name = "steam-condenser"
|
17
|
-
s.date = Time.now
|
18
|
-
s.description = s.summary = 'A multi-language library for querying Source, GoldSrc servers and Steam master servers'
|
19
16
|
s.authors = ['Sebastian Staudt']
|
20
17
|
s.email = 'koraktor@gmail.com'
|
21
|
-
s.
|
22
|
-
|
23
|
-
s.
|
18
|
+
s.description = 'A multi-language library for querying the Steam Community, Source, GoldSrc servers and Steam master servers'
|
19
|
+
s.date = Time.now
|
20
|
+
s.homepage = 'http://koraktor.github.com/steam-condenser'
|
21
|
+
s.name = s.rubyforge_project = "steam-condenser"
|
22
|
+
s.summary = 'Steam Condenser - A Steam query library'
|
23
|
+
|
24
|
+
s.files = %w(README.md Rakefile LICENSE VERSION.yml) + src_files + test_files
|
24
25
|
s.rdoc_options = ["--all", "--inline-source", "--line-numbers", "--charset=utf-8", "--webcvs=http://github.com/koraktor/steam-condenser/source/blob/master/ruby/%s"]
|
26
|
+
|
27
|
+
s.add_dependency('hpricot', '>= 0.6')
|
25
28
|
end
|
26
29
|
|
27
30
|
# Create a rake task +:rdoc+ to build the documentation
|
@@ -31,7 +34,7 @@ Rake::RDocTask.new do |rdoc|
|
|
31
34
|
rdoc.rdoc_files.include ["lib/**/*.rb", "test/**/*.rb", "LICENSE", "README.md"]
|
32
35
|
rdoc.main = "README.md"
|
33
36
|
rdoc.rdoc_dir = "rdoc"
|
34
|
-
rdoc.options = ["--all", "--inline-source", "--line-numbers", "--charset=utf-8", "--webcvs=http://github.com/koraktor/steam-condenser/
|
37
|
+
rdoc.options = ["--all", "--inline-source", "--line-numbers", "--charset=utf-8", "--webcvs=http://github.com/koraktor/steam-condenser/blob/master/ruby/%s"]
|
35
38
|
end
|
36
39
|
|
37
40
|
# Task for cleaning documentation and package directories
|
data/VERSION.yml
ADDED
data/lib/byte_buffer.rb
CHANGED
@@ -1,7 +1,7 @@
|
|
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, Sebastian Staudt
|
4
|
+
# Copyright (c) 2008-2009, Sebastian Staudt
|
5
5
|
|
6
6
|
require "exceptions/buffer_underflow_exception"
|
7
7
|
|
@@ -11,71 +11,71 @@ class ByteBuffer
|
|
11
11
|
attr_accessor :limit
|
12
12
|
attr_reader :position
|
13
13
|
protected :initialize
|
14
|
-
|
14
|
+
|
15
15
|
# Creates a new buffer with the given size in bytes
|
16
16
|
def self.allocate(length)
|
17
17
|
return ByteBuffer.new(0.chr * length)
|
18
18
|
end
|
19
|
-
|
20
|
-
# Creates a new buffer from an existing String
|
19
|
+
|
20
|
+
# Creates a new buffer from an existing String
|
21
21
|
def self.wrap(byte_array)
|
22
22
|
return ByteBuffer.new(byte_array.to_s)
|
23
23
|
end
|
24
|
-
|
25
|
-
#
|
24
|
+
|
25
|
+
#
|
26
26
|
def initialize(byte_array)
|
27
27
|
if byte_array.is_a? ByteBuffer
|
28
28
|
raise Exception.new
|
29
29
|
end
|
30
|
-
|
30
|
+
|
31
31
|
@byte_array = byte_array.to_s
|
32
32
|
@capacity = @byte_array.length
|
33
33
|
@limit = @capacity
|
34
34
|
@position = 0
|
35
35
|
@mark = -1
|
36
36
|
end
|
37
|
-
|
37
|
+
|
38
38
|
def array
|
39
39
|
return @byte_array
|
40
40
|
end
|
41
|
-
|
41
|
+
|
42
42
|
def clear
|
43
43
|
@limit = @capacity
|
44
44
|
@position = 0
|
45
45
|
@mark = -1
|
46
|
-
|
46
|
+
|
47
47
|
return self
|
48
48
|
end
|
49
|
-
|
49
|
+
|
50
50
|
def flip
|
51
51
|
@limit = @position
|
52
52
|
@position = 0
|
53
53
|
@mark = -1
|
54
|
-
|
54
|
+
|
55
55
|
return self
|
56
56
|
end
|
57
|
-
|
57
|
+
|
58
58
|
def get(length = nil)
|
59
59
|
if length == nil
|
60
60
|
length = @limit - @position
|
61
61
|
elsif length > self.remaining
|
62
62
|
BufferUnderflowException.new
|
63
63
|
end
|
64
|
-
|
64
|
+
|
65
65
|
data = @byte_array[@position, length]
|
66
66
|
@position += length
|
67
|
-
|
67
|
+
|
68
68
|
return data
|
69
69
|
end
|
70
|
-
|
70
|
+
|
71
71
|
def get_byte
|
72
72
|
return self.get(1)[0]
|
73
73
|
end
|
74
|
-
|
74
|
+
|
75
75
|
def get_float
|
76
76
|
return self.get(4).unpack("e")[0]
|
77
77
|
end
|
78
|
-
|
78
|
+
|
79
79
|
def get_long
|
80
80
|
return self.get(4).unpack("V")[0]
|
81
81
|
end
|
@@ -87,39 +87,39 @@ class ByteBuffer
|
|
87
87
|
def get_signed_long
|
88
88
|
return self.get(4).unpack('l')[0]
|
89
89
|
end
|
90
|
-
|
90
|
+
|
91
91
|
def get_string
|
92
92
|
zero_byte_index = @byte_array.index("\0", @position)
|
93
93
|
if zero_byte_index == nil
|
94
|
-
|
94
|
+
nil
|
95
95
|
else
|
96
96
|
data_string = self.get(zero_byte_index - @position)
|
97
|
-
@position += 1
|
98
|
-
|
97
|
+
@position += 1
|
98
|
+
data_string
|
99
99
|
end
|
100
100
|
end
|
101
|
-
|
101
|
+
|
102
102
|
def length
|
103
103
|
return @byte_array.length
|
104
104
|
end
|
105
|
-
|
105
|
+
|
106
106
|
def remaining
|
107
107
|
return @limit - @position
|
108
108
|
end
|
109
|
-
|
109
|
+
|
110
110
|
def rewind
|
111
111
|
@position = 0
|
112
112
|
@mark = -1
|
113
|
-
|
113
|
+
|
114
114
|
return self
|
115
115
|
end
|
116
|
-
|
116
|
+
|
117
117
|
def put(source_byte_array)
|
118
118
|
new_position = [source_byte_array.length, self.remaining].min
|
119
119
|
|
120
120
|
@byte_array[@position, new_position] = source_byte_array
|
121
121
|
@position = new_position
|
122
|
-
|
122
|
+
|
123
123
|
return self
|
124
124
|
end
|
125
125
|
|
data/lib/datagram_channel.rb
CHANGED
@@ -1,11 +1,12 @@
|
|
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, Sebastian Staudt
|
5
|
-
|
6
|
-
|
4
|
+
# Copyright (c) 2008-2009, Sebastian Staudt
|
5
|
+
|
6
|
+
require 'ipaddr'
|
7
|
+
require 'socket'
|
7
8
|
|
8
|
-
require
|
9
|
+
require 'byte_buffer'
|
9
10
|
|
10
11
|
class DatagramChannel
|
11
12
|
|
@@ -0,0 +1,12 @@
|
|
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) 2009, Sebastian Staudt
|
5
|
+
|
6
|
+
class RCONBanException < Exception
|
7
|
+
|
8
|
+
def initialize
|
9
|
+
super 'You have been banned from this server.'
|
10
|
+
end
|
11
|
+
|
12
|
+
end
|
data/lib/socket_channel.rb
CHANGED
@@ -3,52 +3,59 @@
|
|
3
3
|
#
|
4
4
|
# Copyright (c) 2008-2009, Sebastian Staudt
|
5
5
|
|
6
|
-
require '
|
6
|
+
require 'ipaddr'
|
7
|
+
require 'socket'
|
7
8
|
require 'timeout'
|
8
9
|
|
10
|
+
require 'byte_buffer'
|
11
|
+
|
9
12
|
class SocketChannel
|
10
|
-
|
13
|
+
|
11
14
|
attr_reader :socket
|
12
|
-
|
15
|
+
|
13
16
|
def self.open
|
14
17
|
return SocketChannel.new
|
15
18
|
end
|
16
|
-
|
19
|
+
|
20
|
+
def close
|
21
|
+
@socket.close
|
22
|
+
end
|
23
|
+
|
17
24
|
def connect(*args)
|
18
25
|
timeout(1) do
|
19
26
|
@socket = TCPSocket.new args[0][0][3], args[0][0][1]
|
20
27
|
@connected = true
|
21
28
|
end
|
22
|
-
|
29
|
+
|
23
30
|
return self
|
24
31
|
end
|
25
|
-
|
32
|
+
|
26
33
|
def initialize
|
27
34
|
@connected = false
|
28
35
|
end
|
29
|
-
|
36
|
+
|
30
37
|
def connected?
|
31
38
|
return @connected
|
32
39
|
end
|
33
|
-
|
40
|
+
|
34
41
|
def read(destination_buffer)
|
35
42
|
if !destination_buffer.is_a? ByteBuffer
|
36
43
|
raise ArgumentError
|
37
44
|
end
|
38
|
-
|
45
|
+
|
39
46
|
length = destination_buffer.remaining
|
40
47
|
data = @socket.recv length
|
41
48
|
destination_buffer.put data
|
42
|
-
|
49
|
+
|
43
50
|
return data.length
|
44
51
|
end
|
45
|
-
|
52
|
+
|
46
53
|
def write(source_buffer)
|
47
54
|
if !source_buffer.is_a? ByteBuffer
|
48
55
|
raise ArgumentError
|
49
56
|
end
|
50
|
-
|
57
|
+
|
51
58
|
return @socket.send(source_buffer.get, 0)
|
52
59
|
end
|
53
|
-
|
54
|
-
end
|
60
|
+
|
61
|
+
end
|
data/lib/steam-condenser.rb
CHANGED
@@ -1,3 +1,14 @@
|
|
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) 2009, Sebastian Staudt
|
5
|
+
|
1
6
|
libdir = File.dirname(__FILE__)
|
2
7
|
$LOAD_PATH.unshift(libdir) unless $LOAD_PATH.include?(libdir)
|
3
8
|
|
9
|
+
module SteamCondenser
|
10
|
+
|
11
|
+
version = YAML.load_file(File.join(File.dirname(__FILE__), '..', 'VERSION.yml'))
|
12
|
+
VERSION = "#{version[:major]}.#{version[:minor]}.#{version[:patch]}"
|
13
|
+
|
14
|
+
end
|
@@ -0,0 +1,95 @@
|
|
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) 2009, Sebastian Staudt
|
5
|
+
|
6
|
+
# This module implements caching functionality to be used in any object using a
|
7
|
+
# +fetch+ method to fetch data, e.g. using a HTTP download.
|
8
|
+
module Cacheable
|
9
|
+
|
10
|
+
def self.included(base) #:nodoc:
|
11
|
+
|
12
|
+
base.extend ClassMethods
|
13
|
+
base.class_eval 'class_variable_set :@@cache, {}'
|
14
|
+
base.class_eval 'class_variable_set :@@cache_ids, {}'
|
15
|
+
|
16
|
+
class << base
|
17
|
+
alias_method :create, :new
|
18
|
+
end
|
19
|
+
|
20
|
+
end
|
21
|
+
|
22
|
+
module ClassMethods
|
23
|
+
|
24
|
+
# Defines wich instance variables should be used to index the cached objects
|
25
|
+
# A call to this method is needed, if you want a class including this module
|
26
|
+
# to really use the cache.
|
27
|
+
def cacheable_with_ids(*ids)
|
28
|
+
class_variable_set(:@@cache_ids, ids)
|
29
|
+
end
|
30
|
+
|
31
|
+
# Returns whether the requested object +id+ is already cached
|
32
|
+
def cached?(id)
|
33
|
+
if id.is_a? String
|
34
|
+
id.downcase!
|
35
|
+
end
|
36
|
+
class_variable_get(:@@cache).key?(id)
|
37
|
+
end
|
38
|
+
|
39
|
+
# Clears the object cache
|
40
|
+
def clear_cache
|
41
|
+
class_variable_set :@@cache, {}
|
42
|
+
end
|
43
|
+
|
44
|
+
# This checks the cache for an existing object. If it exists it is returned.
|
45
|
+
# Otherwise a new object is created.
|
46
|
+
# Overrides the default constructor.
|
47
|
+
def new(id, fetch = true, bypass_cache = false)
|
48
|
+
if cached?(id) and !bypass_cache
|
49
|
+
object = class_variable_get(:@@cache)[id]
|
50
|
+
object.fetch_data if fetch and !object.fetched?
|
51
|
+
object
|
52
|
+
else
|
53
|
+
super(id, fetch)
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
end
|
58
|
+
|
59
|
+
attr_reader :fetch_time
|
60
|
+
|
61
|
+
# Creates a new object for the given +id+, either numeric or
|
62
|
+
# the custom URL specified by the user. If +fetch+ is +true+ (default),
|
63
|
+
# fetch is used to load data into the object.
|
64
|
+
# This method is overridden by Cacheable::ClassMethods#new.
|
65
|
+
def initialize(fetch = true) #:notnew:
|
66
|
+
self.fetch if fetch
|
67
|
+
cache
|
68
|
+
end
|
69
|
+
|
70
|
+
# Saves this object in the cache
|
71
|
+
def cache
|
72
|
+
cache = self.class.class_eval 'class_variable_get :@@cache'
|
73
|
+
cache_ids = self.class.class_eval 'class_variable_get :@@cache_ids'
|
74
|
+
|
75
|
+
cache_ids.each do |cache_id|
|
76
|
+
cache_id_value = instance_variable_get('@' + cache_id.to_s)
|
77
|
+
unless cache_id_value.nil? or cache.key?(cache_id_value)
|
78
|
+
cache[cache_id_value] = self
|
79
|
+
end
|
80
|
+
end
|
81
|
+
|
82
|
+
true
|
83
|
+
end
|
84
|
+
|
85
|
+
# Sets the time this object has been fetched the last time
|
86
|
+
def fetch
|
87
|
+
@fetch_time = Time.now
|
88
|
+
end
|
89
|
+
|
90
|
+
# Returns whether the data for this SteamID has already been fetched
|
91
|
+
def fetched?
|
92
|
+
!@fetch_time.nil?
|
93
|
+
end
|
94
|
+
|
95
|
+
end
|
@@ -0,0 +1,124 @@
|
|
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) 2009, Sebastian Staudt
|
5
|
+
|
6
|
+
require 'steam/community/game_stats'
|
7
|
+
|
8
|
+
# The DefenseGridStats class represents the game statistics for a single user in
|
9
|
+
# Defense Grid: The Awakening
|
10
|
+
class DefenseGridStats < GameStats
|
11
|
+
|
12
|
+
attr_reader :bronze_medals, :damage_done, :damage_campaign, :damage_challenge,
|
13
|
+
:encountered, :gold_medals, :heat_damage, :interest, :killed,
|
14
|
+
:killed_campaign, :killed_challenge, :levels_played,
|
15
|
+
:levels_played_campaign, :levels_played_challenge, :levels_won,
|
16
|
+
:levels_won_campaign, :levels_won_challenge, :orbital_laser_fired,
|
17
|
+
:orbital_laser_damage, :resources, :silver_medals, :time_played
|
18
|
+
|
19
|
+
# Creates a DefenseGridStats object by calling the super constructor with the
|
20
|
+
# game name "defensegrid:awakening"
|
21
|
+
def initialize(steam_id)
|
22
|
+
super(steam_id, 'defensegrid:awakening')
|
23
|
+
|
24
|
+
if public?
|
25
|
+
general_data = @xml_data.elements['stats/general']
|
26
|
+
|
27
|
+
@bronze_medals = general_data.elements['bronze_medals_won/value'].text.to_i
|
28
|
+
@silver_medals = general_data.elements['silver_medals_won/value'].text.to_i
|
29
|
+
@gold_medals = general_data.elements['gold_medals_won/value'].text.to_i
|
30
|
+
@levels_played = general_data.elements['levels_played_total/value'].text.to_i
|
31
|
+
@levels_played_campagin = general_data.elements['levels_played_campaign/value'].text.to_i
|
32
|
+
@levels_played_challenge = general_data.elements['levels_played_challenge/value'].text.to_i
|
33
|
+
@levels_won = general_data.elements['levels_won_total/value'].text.to_i
|
34
|
+
@levels_won_campaign = general_data.elements['levels_won_campaign/value'].text.to_i
|
35
|
+
@levels_won_challenge = general_data.elements['levels_won_challenge/value'].text.to_i
|
36
|
+
@encountered = general_data.elements['total_aliens_encountered/value'].text.to_i
|
37
|
+
@killed = general_data.elements['total_aliens_killed/value'].text.to_i
|
38
|
+
@killed_campaign = general_data.elements['total_aliens_killed_campaign/value'].text.to_i
|
39
|
+
@killed_challenge = general_data.elements['total_aliens_killed_challenge/value'].text.to_i
|
40
|
+
@resources = general_data.elements['resources_recovered/value'].text.to_i
|
41
|
+
@heat_damage = general_data.elements['heatdamage/value'].text.to_f
|
42
|
+
@time_played = general_data.elements['time_played/value'].text.to_f
|
43
|
+
@interest = general_data.elements['interest_gained/value'].text.to_f
|
44
|
+
@damage = general_data.elements['tower_damage_total/value'].text.to_f
|
45
|
+
@damage_campaign = general_data.elements['tower_damage_total_campaign/value'].text.to_f
|
46
|
+
@damage_challenge = general_data.elements['tower_damage_total_challenge/value'].text.to_f
|
47
|
+
@orbital_laser_fired = @xml_data.elements['stats/orbitallaser/fired/value'].text.to_i
|
48
|
+
@orbital_laser_damage = @xml_data.elements['stats/orbitallaser/damage/value'].text.to_f
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
# Returns stats about the towers built
|
53
|
+
#
|
54
|
+
# The Hash returned uses the names of the aliens as keys. Every value of the
|
55
|
+
# Hash is an Array containing the number of aliens encountered as the first
|
56
|
+
# element and the number of aliens killed as the second element.
|
57
|
+
def alien_stats
|
58
|
+
return unless public?
|
59
|
+
|
60
|
+
if @alien_stats.nil?
|
61
|
+
alien_data = @xml_data.elements['stats/aliens']
|
62
|
+
@alien_stats = {}
|
63
|
+
aliens = %w{swarmer juggernaut crasher spire grunt bulwark drone manta dart
|
64
|
+
decoy rumbler seeker turtle walker racer stealth}
|
65
|
+
|
66
|
+
aliens.each do |alien|
|
67
|
+
@alien_stats[alien] = [
|
68
|
+
alien_data.elements["#{alien}/encountered/value"].text.to_i,
|
69
|
+
alien_data.elements["#{alien}/killed/value"].text.to_i
|
70
|
+
]
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
74
|
+
@alien_stats
|
75
|
+
end
|
76
|
+
|
77
|
+
# Returns stats about the towers built
|
78
|
+
#
|
79
|
+
# The Hash returned uses the names of the towers as keys. Every value of
|
80
|
+
# the Hash is another Hash using the keys 1 to 3 for different tower levels.
|
81
|
+
# The values of these Hash is an Array containing the number of towers built
|
82
|
+
# as the first element and the damage dealt by this specific tower type as the
|
83
|
+
# second element.
|
84
|
+
#
|
85
|
+
# The Command tower uses the resources gained as second element.
|
86
|
+
# The Temporal tower doesn't have a second element.
|
87
|
+
def tower_stats
|
88
|
+
return unless public?
|
89
|
+
|
90
|
+
if @tower_stats.nil?
|
91
|
+
tower_data = @xml_data.elements['stats/towers']
|
92
|
+
@tower_stats = {}
|
93
|
+
towers = %w{cannon flak gun inferno laser meteor missile tesla}
|
94
|
+
|
95
|
+
towers.each do |tower|
|
96
|
+
@tower_stats[tower] = {}
|
97
|
+
(1..3).each do |i|
|
98
|
+
@tower_stats[tower][i] = [
|
99
|
+
tower_data.elements["#{tower}[@level=#{i}]/built/value"].text.to_i,
|
100
|
+
tower_data.elements["#{tower}[@level=#{i}]/damage/value"].text.to_f
|
101
|
+
]
|
102
|
+
end
|
103
|
+
end
|
104
|
+
|
105
|
+
@tower_stats['command'] = {}
|
106
|
+
(1..3).each do |i|
|
107
|
+
@tower_stats['command'][i] = [
|
108
|
+
tower_data.elements["command[@level=#{i}]/built/value"].text.to_i,
|
109
|
+
tower_data.elements["command[@level=#{i}]/resource/value"].text.to_f
|
110
|
+
]
|
111
|
+
end
|
112
|
+
|
113
|
+
@tower_stats['temporal'] = {}
|
114
|
+
(1..3).each do |i|
|
115
|
+
@tower_stats['temporal'][i] = [
|
116
|
+
tower_data.elements["temporal[@level=#{i}]/built/value"].text.to_i,
|
117
|
+
]
|
118
|
+
end
|
119
|
+
end
|
120
|
+
|
121
|
+
@tower_stats
|
122
|
+
end
|
123
|
+
|
124
|
+
end
|