wowr 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
data/lib/wowr.rb ADDED
@@ -0,0 +1,350 @@
1
+ # Wowr - Ruby library for the World of Warcraft Armory
2
+ # http://wowr.rubyforge.org/
3
+
4
+ # written by Ben Humphreys
5
+ # http://benhumphreys.co.uk/
6
+
7
+ begin
8
+ require 'lib/hpricot-0.6/hpricot'
9
+ rescue LoadError
10
+ require 'rubygems'
11
+ require 'hpricot'
12
+ end
13
+ require 'net/http'
14
+ require 'cgi'
15
+
16
+ # TODO: what does this do?
17
+ $:.unshift(File.dirname(__FILE__)) unless $:.include?(File.dirname(__FILE__)) || $:.include?(File.expand_path(File.dirname(__FILE__)))
18
+
19
+ require 'lib/exceptions'
20
+ require 'lib/extensions'
21
+ require 'lib/classes'
22
+
23
+ module Wowr
24
+ class API
25
+
26
+ @@armory_url = 'http://www.wowarmory.com/'
27
+ @@eu_armory_url = 'http://eu.wowarmory.com/'
28
+
29
+ @@character_sheet_url = 'character-sheet.xml'
30
+ @@character_talents_url = 'character-talents.xml'
31
+ @@character_skills_url = 'character-skills.xml'
32
+ @@character_reputation_url = 'character-reputation.xml'
33
+
34
+ @@guild_info_url = 'guild-info.xml'
35
+
36
+ @@search_url = 'search.xml'
37
+
38
+ @@item_info_url = 'item-info.xml'
39
+ @@item_tooltip_url = 'item-tooltip.xml'
40
+
41
+ @@icon_url = 'http://wowbench.com/images/icons/32x32/'
42
+
43
+ @@arena_team_url = 'team-info.xml'
44
+
45
+ @@max_connection_tries = 10
46
+
47
+ # Part of the rails plugin stuff?
48
+ # @@profession_icons = {
49
+ # :alchemy => "Trade_Alchemy.png",
50
+ # :blacksmithing => "Trade_BlackSmithing.png",
51
+ # :engineering => "Trade_Engineering.png",
52
+ # :enchanting => "Trade_Engraving.png",
53
+ # :jewelcrafting => "INV_Hammer_21.png",
54
+ # :herbalism => "Trade_Herbalism.png",
55
+ # :leatherworking => "Trade_LeatherWorking.png",
56
+ # :mining => "Trade_Mining.png",
57
+ # :tailoring => "Trade_Tailoring.png",
58
+ # :skinning => "INV_Weapon_ShortBlade_01.png"
59
+ # }
60
+
61
+ @@classes = {
62
+ 1 => 'Warrior',
63
+ 2 => 'Paladin',
64
+ 3 => 'Hunter',
65
+ 4 => 'Rogue',
66
+ 5 => 'Priest',
67
+ #6 => 'Gold Farmer', # there is no class 6
68
+ 7 => 'Shaman',
69
+ 8 => 'Mage',
70
+ 9 => 'Warlock',
71
+ #10 => 'Purveyor of Baked Goods', # there is no class 10
72
+ 11 => 'Druid'
73
+ # 12 => 'Death Knight'?
74
+ }
75
+
76
+ @@genders = {
77
+ 0 => 'Male',
78
+ 1 => 'Female'
79
+ }
80
+
81
+ @@races = {
82
+ 1 => 'Human',
83
+ 1 => 'Orc',
84
+ 3 => 'Dwarf',
85
+ 4 => 'Night Elf',
86
+ 5 => 'Undead',
87
+ 6 => 'Tauren',
88
+ 7 => 'Gnome',
89
+ 8 => 'Troll',
90
+ #9 => 'Pandaren', # there is no race 9
91
+ 10 => 'Blood Elf',
92
+ 11 => 'Draenei'
93
+ }
94
+
95
+ @@search_types = {
96
+ #:all => 'all', # TODO: All is too complex at the moment, API doesn't return all results in one query
97
+ :item => 'items',
98
+ :character => 'characters',
99
+ :guild => 'guilds',
100
+ :arena_team => 'arenateams'
101
+ }
102
+
103
+ @@arena_team_sizes = [2, 3, 5]
104
+
105
+ # TODO: Refactor, rethink and remove locale from all requests by passing unused options on to get_xml
106
+
107
+ attr_accessor :character_name, :guild_name, :realm, :locale
108
+
109
+ # You can set up the API with an optional default guild and realm
110
+ # These will be used in all your API requests unless you specify otherwise
111
+ # For item requests, the locale will not matter in results, but may affect the speed of replies
112
+ # TODO: are these nil declarations pointless?
113
+ def initialize(options = {:character_name => nil, :guild_name => nil, :realm => nil, :locale => :us})
114
+ @character_name = options[:character_name]
115
+ @guild_name = options[:guild_name]
116
+ @realm = options[:realm]
117
+ @locale = options[:locale] #|| :us
118
+ end
119
+
120
+
121
+ # General-purpose search
122
+ def search(options = {:search => nil, :type => nil})
123
+ if @@search_types.include? options[:type]
124
+ raise Wowr::Exceptions::InvalidSearchType.new
125
+ end
126
+
127
+ puts options.to_yaml
128
+
129
+ if options[:search].nil?
130
+ raise Wowr::Exceptions::NoSearchString.new
131
+ end
132
+
133
+ xml = get_xml(@@search_url, options)
134
+
135
+ puts xml
136
+
137
+ results = []
138
+
139
+ if (xml%'armorySearch'%'searchResults')
140
+ case options[:type]
141
+
142
+ # TODO: Filter stuff
143
+ when @@search_types[:item]
144
+ (xml%'armorySearch'%'searchResults'%'items'/:item).each do |item|
145
+ results << Wowr::Classes::SearchItem.new(item)
146
+ end
147
+
148
+ when @@search_types[:character]
149
+ (xml%'armorySearch'%'searchResults'%'characters'/:character).each do |char|
150
+ results << Wowr::Classes::Character.new(char)
151
+ end
152
+
153
+ when @@search_types[:guild]
154
+ (xml%'armorySearch'%'searchResults'%'guilds'/:guild).each do |guild|
155
+ results << Wowr::Classes::Guild.new(guild)
156
+ end
157
+
158
+ when @@search_types[:arena_team]
159
+ (xml%'armorySearch'%'searchResults'%'arenaTeams'/:arenaTeam).each do |team|
160
+ results << Wowr::Classes::ArenaTeam.new(team)
161
+ end
162
+ end
163
+ end
164
+
165
+ return results
166
+ end
167
+
168
+
169
+
170
+ # Characters
171
+ # Note searches go across all realms by default
172
+ def search_characters(options = {:name => @character_name})
173
+ options.merge!(:type => @@search_types[:character])
174
+ return search(options)
175
+ end
176
+
177
+ def get_character_sheet(options = {:character_name => @character_name, :realm => @realm})
178
+ xml = get_xml(@@character_sheet_url, options)
179
+ return Wowr::Classes::CharacterSheet.new(xml)
180
+ end
181
+
182
+ def get_character_list(min_level, klass)
183
+
184
+ end
185
+
186
+ # ??
187
+ def get_character_skills(options = {:character_name => @character_name, :realm => @realm})
188
+ compute_hash(options.merge(:url => @@character_skills_url)) ||
189
+ process_query(Wowr::Classes::Skill, @character_skills_url, false)
190
+ end
191
+
192
+
193
+ # Guilds
194
+ def search_guilds(options = {:search => @guild_name, :locale => @locale})
195
+ options.merge!(:type => @@search_types[:guild])
196
+ return search(options)
197
+ end
198
+
199
+ def get_guild(options = {:guild_name => @guild_name, :realm => @realm})
200
+ xml = get_xml(@@guild_info_url, options)
201
+ return Wowr::Classes::Guild.new(xml)
202
+ end
203
+
204
+
205
+
206
+ # Items
207
+ def search_items(options = {:search => nil})
208
+ options.merge!(:type => @@search_types[:item])
209
+ return search(options)
210
+ end
211
+
212
+ # TODO: Is not finding the item an exception or just return nil?
213
+ def get_item(options = {:item_id => nil, :locale => @locale})
214
+
215
+ #return Wowr::Classes::ItemTooltip.new(xml%'itemTooltip')
216
+ end
217
+
218
+ def get_item_info(options = {:item_id => nil, :locale => @locale})
219
+ xml = get_xml(@@item_info_url, options)
220
+ if (xml%'itemInfo'%'item')
221
+ return Wowr::Classes::ItemInfo.new(xml%'itemInfo'%'item')
222
+ else
223
+ return nil
224
+ end
225
+ end
226
+
227
+ def get_item_tooltip(options = {:item_id => nil})
228
+ xml = get_xml(@@item_tooltip_url, options)
229
+
230
+ # tooltip returns empty document when not found
231
+ if xml.nil?
232
+ return nil
233
+ #raise Wowr::Exceptions::ItemNotFound.new("Item not found with id: #{options[:item_id]}")
234
+ end
235
+ return Wowr::Classes::ItemTooltip.new(xml%'itemTooltip')
236
+ end
237
+
238
+
239
+
240
+ # Arena Teams
241
+ def search_arena_teams(options = {})
242
+ options.merge!(:type => @@search_types[:arena_team])
243
+ return search(options)
244
+ end
245
+
246
+ def get_arena_team(options = {:team_name => :nil, :team_size => nil, :realm => @realm})
247
+ if !@@arena_team_sizes.include?(options[:team_size])
248
+ raise Wowr::Exceptions::InvalidArenaTeamSize.new("Arena teams size must be: #{@@arena_team_sizes.inspect}")
249
+ end
250
+
251
+ xml = get_xml(@@arena_team_url, options)
252
+ return Wowr::Classes::ArenaTeam.new(xml%'arenaTeam')
253
+ end
254
+
255
+ # this is more of a rails-plugin thing
256
+ # def icon(icon_url)
257
+ # @@icon_url + icon_url
258
+ # end
259
+
260
+
261
+ protected
262
+ def base_url(locale = @locale)
263
+ if locale == :eu
264
+ @@eu_armory_url
265
+ else
266
+ @@armory_url
267
+ end
268
+ end
269
+
270
+ # TODO: pretty damn hacky
271
+ def get_xml(url, opts = {})
272
+
273
+ # better way of doing this?
274
+ reqs = {
275
+ :character_name => 'n',
276
+ :realm => 'r',
277
+ :search => 'searchQuery',
278
+ :type => 'searchType',
279
+ :guild_name => 'n',
280
+ :item_id => 'i',
281
+ :team_size => 'ts',
282
+ :team_name => 't'
283
+ }
284
+
285
+ params = []
286
+ opts.each do |key, value|
287
+ if reqs[key]
288
+ params << "#{reqs[key]}=#{u(value)}"
289
+ end
290
+ end
291
+
292
+ if params.size > 0
293
+ query = '?' + params.join('&')
294
+ end
295
+
296
+ locale = opts[:locale] || @locale
297
+
298
+ base = self.base_url(locale)
299
+ uri = URI.parse(base + url)
300
+ full_query = base + url + query
301
+ #puts full_query
302
+ req = Net::HTTP::Get.new(full_query)
303
+ req["user-agent"] = "Mozilla/5.0 Gecko/20070219 Firefox/2.0.0.2"
304
+ res = Net::HTTP.new(uri.host, uri.port).start {|http| http.request(req) }
305
+
306
+ tries = 0
307
+ response = case res
308
+ when Net::HTTPSuccess, Net::HTTPRedirection
309
+ res.body
310
+ else
311
+ tries += 1
312
+ if tries > @@max_connection_tries
313
+ raise Wowr::Exceptions::NetworkTimeout.new('Timed out')
314
+ else
315
+ retry
316
+ end
317
+ end
318
+
319
+ #puts response
320
+
321
+ doc = Hpricot.XML(response)
322
+ begin
323
+ errors = doc.search("*[@errCode]")
324
+ #errors.to_yaml
325
+ if errors.size > 0
326
+ errors.each do |error|
327
+ raise Wowr::Exceptions::raise_me(error[:errCode])
328
+ end
329
+ else
330
+ return (doc%'page')
331
+ end
332
+ rescue Exception => e
333
+ $stderr.puts "Fatal error ((#{e.to_s})): Couldn't search the XML document."
334
+ $stderr.puts doc
335
+ exit 1
336
+ end
337
+
338
+ end
339
+
340
+ # TODO: URL encoding of query strings
341
+ def u(str)
342
+ if str.instance_of?(String)
343
+ return CGI.escape(str)
344
+ else
345
+ return str
346
+ end
347
+ #stripslashes(str)
348
+ end
349
+ end
350
+ end
@@ -0,0 +1,1299 @@
1
+ # TODO: Item sources - Vendors
2
+ # sourceType.vendor
3
+ # sourceType.questReward
4
+ # sourceType.createdBySpell
5
+
6
+ # TODO: Split up classes depending on subject?
7
+ # Character, Item, Guild, ArenaTeam
8
+
9
+
10
+ # Wowr was written by Ben Humphreys!
11
+ # http://wowr.benhumphreys.co.uk/
12
+ module Wowr
13
+ module Classes
14
+
15
+ # Composed of an ItemInfo and
16
+ # Needs to be consolidated with ItemInfo and other stuff
17
+ # to be a parent class that they extend?
18
+ class Item
19
+ attr_reader :id, :name, :icon
20
+ alias_method :item_id, :id
21
+
22
+ def initialize(elem)
23
+ @id = elem[:id].to_i
24
+ @name = elem[:name]
25
+ @icon = elem[:icon]
26
+ end
27
+ end
28
+
29
+
30
+ # Short character info, used in guild lists etc.
31
+ class Character
32
+ attr_reader :name, :level, :url, :rank,
33
+ :klass, :klass_id,
34
+ :gender, :gender_id,
35
+ :race, :race_id,
36
+ :guild, :guild_id,
37
+ :battle_group, :last_login,
38
+ :relevance, :search_rank,
39
+
40
+ :season_games_played, :season_games_won, :team_rank # From ArenaTeam info
41
+
42
+ def initialize(elem)
43
+ @name = elem[:name]
44
+ @level = elem[:level].to_i
45
+ @url = elem[:url] || elem[:charUrl]
46
+ @rank = elem[:rank].to_i
47
+
48
+ @klass = elem[:class]
49
+ @klass_id = elem[:classId].to_i
50
+
51
+ @gender = elem[:gender]
52
+ @gender_id = elem[:genderId].to_i
53
+
54
+ @race = elem[:race]
55
+ @race_id = elem[:raceId].to_i
56
+
57
+ @guild = elem[:guild] == "" ? nil : elem[:guild]
58
+ @guild_id = elem[:guildId].to_i == 0 ? nil : elem[:guildId].to_i
59
+
60
+ @battle_group = elem[:battleGroup]
61
+ @battle_group_id = elem[:battleGroupId].to_i
62
+
63
+ @relevance = elem[:relevance].to_i
64
+ @search_rank = elem[:searchRank].to_i
65
+
66
+ @last_login = elem[:lastLoginDate] == "" ? nil : elem[:lastLoginDate]
67
+
68
+ # From ArenaTeam info, can be blank on normal requests
69
+ @season_games_played = elem[:seasonGamesPlayed].to_i
70
+ @season_games_won = elem[:seasonGamesWon].to_i
71
+ @team_rank = elem[:teamRank].to_i
72
+ #@char_url = elem[:charUrl] # TODO: Merge with URL?
73
+ end
74
+ end
75
+
76
+
77
+
78
+ # Full character details
79
+ # uses characterInfo element
80
+ class CharacterSheet
81
+
82
+ # character_info
83
+ attr_reader :name, :level, :char_url, :title,
84
+ :gender, :gender_id,
85
+ :race, :race_id,
86
+ :klass, :klass_id,
87
+ :faction, :faction_id,
88
+ :guild_name, :guild_url,
89
+ :realm,
90
+ :battle_group,
91
+ :last_modified
92
+
93
+ # character_tab
94
+ attr_reader :health, :second_bar,
95
+ :strength, :agility, :stamina, :intellect, :spirit
96
+ alias_method :str, :strength
97
+ alias_method :agi, :agility
98
+ alias_method :sta, :stamina
99
+ alias_method :int, :intellect
100
+ alias_method :spi, :spirit
101
+
102
+ attr_reader :melee, :ranged, :spell,
103
+ :defenses, :resistances,
104
+ :talent_spec, :pvp,
105
+ :professions,
106
+ :items,
107
+ :buffs, :debuffs
108
+
109
+ # It's made up of two parts
110
+ # Don't care about battlegroups yet
111
+ # I don't think I can call stuff from the constructor?
112
+ def initialize(elem)
113
+ character_info(elem%'character')
114
+ character_tab(elem%'characterTab')
115
+ end
116
+
117
+
118
+ def character_info(elem)
119
+ # basic info
120
+ @name = elem[:name]
121
+ @level = elem[:level].to_i
122
+ @char_url = elem[:url]
123
+ @rank = elem[:rank].to_i
124
+ @title = elem[:title]
125
+
126
+ @klass = elem[:class]
127
+ @klass_id = elem[:classId].to_i
128
+
129
+ @gender = elem[:gender]
130
+ @gender_id = elem[:genderId].to_i
131
+
132
+ @race = elem[:race]
133
+ @race_id = elem[:raceId].to_i
134
+
135
+ @faction = elem[:faction]
136
+ @faction_id = elem[:factionId].to_i
137
+
138
+ @guild = elem[:guild]
139
+ @guild_url = elem[:guildUrl]
140
+
141
+ @realm = elem[:realm]
142
+
143
+ @battle_group = elem[:battleGroup]
144
+
145
+ @last_modified = elem[:lastModified]#.to_time
146
+ end
147
+
148
+ def character_tab(elem)
149
+ @health = (elem%'characterBars'%'health')[:effective].to_i
150
+ @second_bar = SecondBar.new(elem%'characterBars'%'secondBar')
151
+
152
+ # base stats
153
+ # % is alias for Hpricot's 'at' method, assume only 1 baseStats/strength element
154
+ @strength = Strength.new(elem%'baseStats'%'strength')
155
+ @agility = Agility.new(elem%'baseStats'%'agility')
156
+ @stamina = Stamina.new(elem%'baseStats'%'stamina')
157
+ @intellect = Intellect.new(elem%'baseStats'%'intellect')
158
+ @spirit = Spirit.new(elem%'baseStats'%'spirit')
159
+
160
+ # damage stuff
161
+ @melee = Melee.new(elem%'melee')
162
+ @ranged = Ranged.new(elem%'ranged')
163
+ @spell = Spell.new(elem.at(' > spell')) # TODO: hacky?
164
+ @defenses = Defenses.new(elem%'defenses')
165
+
166
+
167
+ #resistances
168
+ #TODO: This keying is wrong
169
+ @resistances = []
170
+ (elem/:resistances).each do |resistance|
171
+ #@resistances[:arcane] = ArcaneResistance.new(elem[:resistances][:arcane])
172
+ #@resistances[resistance] = Resistance.new(resistance)
173
+ end
174
+
175
+
176
+ @talent_spec = TalentSpec.new(elem%'talentSpec')
177
+
178
+ @pvp = Pvp.new(elem%'pvp')
179
+
180
+
181
+
182
+ # professions
183
+ @professions = []
184
+ (elem%'professions'/:skill).each do |skill|
185
+ @professions << Profession.new(skill)
186
+ end
187
+
188
+ # items
189
+ @items = []
190
+ (elem%'items'/:item).each do |item|
191
+ @items << EquippedItem.new(item)
192
+ end
193
+
194
+ # buffs
195
+ @buffs = []
196
+ (elem%'buffs'/:spell).each do |buff|
197
+ @buffs << Buff.new(buff)
198
+ end
199
+
200
+ # debuffs
201
+ @debuffs = []
202
+ (elem%'debuffs'/:spell).each do |debuff|
203
+ @debuffs << Buff.new(debuff)
204
+ end
205
+ end
206
+ end
207
+
208
+
209
+
210
+ # Note differences in values depending on class
211
+ class SecondBar
212
+ attr_reader :effective, :casting, :not_casting, :type
213
+
214
+ def initialize(elem)
215
+ @effective = elem[:effective].to_i
216
+ @casting = elem[:casting].to_i == -1 ? nil : elem[:casting].to_i
217
+ @not_casting = elem[:notCasting].to_i == -1 ? nil : elem[:notCasting].to_i
218
+ @type = elem[:type]
219
+ end
220
+ end
221
+
222
+
223
+
224
+ class BaseStat # abstract?
225
+ attr_reader :base, :effective
226
+ end
227
+
228
+ class Strength < BaseStat
229
+ attr_reader :attack, :block
230
+ def initialize(elem)
231
+ @base = elem['base'].to_i
232
+ @effective = elem['effective'].to_i
233
+ @attack = elem['attack'].to_i
234
+ @block = elem['block'].to_i == -1 ? nil : elem['block'].to_i
235
+ end
236
+ end
237
+
238
+ class Agility < BaseStat
239
+ attr_reader :armor, :attack, :crit_hit_percent
240
+ def initialize(elem)
241
+ @base = elem[:base].to_i
242
+ @effective = elem[:effective].to_i
243
+ @armor = elem[:armor].to_i
244
+ @attack = elem[:attack].to_i == -1 ? nil : elem[:attack].to_i
245
+ @crit_hit_percent = elem[:critHitPercent].to_f
246
+ end
247
+ end
248
+
249
+ class Stamina < BaseStat
250
+ attr_reader :health, :pet_bonus
251
+ def initialize(elem)
252
+ @base = elem[:base].to_i
253
+ @effective = elem[:effective].to_i
254
+ @health = elem[:health].to_i
255
+ @pet_bonus = elem[:petBonus].to_i == -1 ? nil : elem[:petBonus].to_i
256
+ end
257
+ end
258
+
259
+ class Intellect < BaseStat
260
+ attr_reader :mana, :crit_hit_percent, :pet_bonus
261
+ def initialize(elem)
262
+ @base = elem[:base].to_i
263
+ @effective = elem[:effective].to_i
264
+ @mana = elem[:mana].to_i
265
+ @crit_hit_percent = elem[:critHitPercent].to_f
266
+ @pet_bonus = elem[:petBonus].to_i == -1 ? nil : elem[:petBonus].to_i
267
+ end
268
+ end
269
+
270
+ class Spirit < BaseStat
271
+ attr_reader :health_regen, :mana_regen
272
+ def initialize(elem)
273
+ @base = elem[:base].to_i
274
+ @effective = elem[:effective].to_i
275
+ @health_regen = elem[:healthRegen].to_i
276
+ @mana_regen = elem[:manaRegen].to_i
277
+ end
278
+ end
279
+
280
+ class Armor < BaseStat
281
+ attr_reader :percent, :pet_bonus
282
+ def initialize(elem)
283
+ @base = elem[:base].to_i
284
+ @effective = elem[:effective].to_i
285
+ @percent = elem[:percent].to_f
286
+ @pet_bonus = elem[:petBonus].to_i == -1 ? nil : elem[:petBonus].to_i
287
+ end
288
+ end
289
+
290
+
291
+
292
+
293
+
294
+ class Melee
295
+ attr_reader :main_hand_skill, :off_hand_skill,
296
+ :main_hand_damage, :off_hand_damage,
297
+ :main_hand_speed, :off_hand_speed,
298
+ :speed, :hit_rating, :crit_chance
299
+
300
+ def initialize(elem)
301
+ # TODO: Do these not exist anymore?
302
+ @main_hand_skill = WeaponSkill.new(elem%'mainHandWeaponSkill') if (elem%'mainHandWeaponSkill')
303
+ @off_hand_skill = WeaponSkill.new(elem%'offHandWeaponSkill') if (elem%'offHandWeaponSkill')
304
+
305
+ @main_hand_damage = WeaponDamage.new(elem%'mainHandDamage')
306
+ @off_hand_damage = WeaponDamage.new(elem%'offHandDamage')
307
+
308
+ @main_hand_speed = WeaponSpeed.new(elem%'mainHandSpeed')
309
+ @off_hand_speed = WeaponSpeed.new(elem%'offHandSpeed')
310
+
311
+ @power = WeaponPower.new(elem%'power')
312
+ @hit_rating = WeaponHitRating.new(elem%'hitRating')
313
+ @crit_chance = WeaponCritChance.new(elem%'critChance')
314
+ end
315
+ end
316
+
317
+ class Ranged
318
+ attr_reader :weapon_skill, :damage, :speed, :power,
319
+ :hit_rating, :crit_chance
320
+
321
+ def initialize(elem)
322
+ @weapon_skill = WeaponSkill.new(elem%'weaponSkill')
323
+ @damage = WeaponDamage.new(elem%'damage')
324
+ @speed = WeaponSpeed.new(elem%'speed')
325
+ @power = WeaponPower.new(elem%'power')
326
+ @hit_rating = WeaponHitRating.new(elem%'hitRating')
327
+ @crit_chance = WeaponCritChance.new(elem%'critChance')
328
+ end
329
+ end
330
+
331
+ class WeaponSkill
332
+ attr_reader :rating, :value
333
+
334
+ def initialize(elem)
335
+ @value = elem[:value].to_i == -1 ? nil : elem[:value].to_i
336
+ @rating = elem[:rating].to_i
337
+ end
338
+ end
339
+
340
+ class WeaponDamage
341
+ attr_reader :dps, :max, :min, :percent, :speed
342
+
343
+ def initialize(elem)
344
+ @dps = elem[:dps].to_f
345
+ @max = elem[:max].to_i
346
+ @min = elem[:min].to_i
347
+ @percent = elem[:percent].to_f
348
+ @speed = elem[:speed].to_f
349
+ end
350
+ end
351
+
352
+ class WeaponSpeed
353
+ attr_reader :haste_percent, :haste_rating, :value
354
+
355
+ def initialize(elem)
356
+ @haste_percent = elem[:hastePercent].to_f
357
+ @haste_rating = elem[:hasteRating].to_f
358
+ @value = elem[:value].to_f
359
+ end
360
+ end
361
+
362
+ class WeaponPower
363
+ attr_reader :base, :effective, :increased_dps, :pet_attack, :pet_spell
364
+
365
+ def initialize(elem)
366
+ @base = elem[:base].to_i
367
+ @haste_rating = elem[:effective].to_i
368
+ @increased_dps = elem[:increasedDps].to_f
369
+ @pet_attack = (elem[:petAttack].to_f == -1 ? nil : elem[:petAttack].to_f)
370
+ @pet_spell = (elem[:petSpell].to_f == -1 ? nil : elem[:petSpell].to_f)
371
+ end
372
+ end
373
+
374
+ class WeaponHitRating
375
+ attr_reader :increased_hit_percent, :value
376
+
377
+ def initialize(elem)
378
+ @increased_hit_percent = elem[:increasedHitPercent].to_f
379
+ @value = elem[:value].to_f
380
+ end
381
+ end
382
+
383
+ class WeaponCritChance
384
+ attr_reader :percent, :plus_percent, :rating
385
+
386
+ def initialize(elem)
387
+ @percent = elem[:percent].to_f
388
+ @plus_percent = elem[:plusPercent].to_f
389
+ @rating = elem[:rating].to_i
390
+ end
391
+ end
392
+
393
+
394
+
395
+ # Decided to do funky stuff to the XML to make it more useful.
396
+ # instead of having two seperate lists of bonusDamage and critChance
397
+ # merged it into one set of objects for each thing
398
+ class Spell
399
+ attr_reader :arcane, :fire, :frost, :holy, :nature, :shadow,
400
+ :hit_rating, :bonus_healing, :penetration, :mana_regen
401
+
402
+ def initialize(elem)
403
+ @arcane = SpellDamage.new(elem%'bonusDamage'%'arcane', elem%'critChance'%'arcane')
404
+ @fire = SpellDamage.new(elem%'bonusDamage'%'fire', elem%'critChance'%'fire')
405
+ @frost = SpellDamage.new(elem%'bonusDamage'%'frost', elem%'critChance'%'frost')
406
+ @holy = SpellDamage.new(elem%'bonusDamage'%'holy', elem%'critChance'%'holy')
407
+ @nature = SpellDamage.new(elem%'bonusDamage'%'nature', elem%'critChance'%'nature')
408
+ @shadow = SpellDamage.new(elem%'bonusDamage'%'shadow', elem%'critChance'%'shadow')
409
+
410
+ @bonus_healing = (elem%'bonusHealing')[:value].to_i # is this right??
411
+ @penetration = (elem%'penetration')[:value].to_i
412
+ @hit_rating = WeaponHitRating.new(elem%'hitRating')
413
+ @mana_regen = ManaRegen.new(elem%'manaRegen')
414
+
415
+ # elements = %w[arcane fire frost holy nature shadow]
416
+ # elements.each do |element|
417
+ # # TODO: is this a good idea?
418
+ # #instance_variable_set("@#{element}", foo) #??
419
+ # #eval("@#{element} = SpellDamage.new(elem[:bonusDamage][element][:value], elem[:critChance][element][:percent]).to_f)")
420
+ # # eval("@#{element} = SpellDamage.new((elem%'bonusDamage'%element)[:value].to_i,
421
+ # # (elem%'critChance'%element)[:percent].to_f)")
422
+ # end
423
+ end
424
+ end
425
+
426
+ class SpellDamage
427
+ attr_reader :value, :crit_chance_percent
428
+ alias_method :percent, :crit_chance_percent
429
+
430
+ def initialize(bonusDamage_elem, critChance_elem)
431
+ @value = bonusDamage_elem[:value].to_i
432
+ @crit_chance_percent = critChance_elem[:percent].to_f
433
+ end
434
+ end
435
+
436
+ class ManaRegen
437
+ attr_reader :casting, :not_casting
438
+
439
+ def initialize(elem)
440
+ @casting = elem[:casting].to_f
441
+ @not_casting = elem[:notCasting].to_f
442
+ end
443
+ end
444
+
445
+ class PetBonus
446
+ attr_reader :attack, :damage, :from_Type
447
+
448
+ def initialize(elem)
449
+ @attack = elem[:attack].to_i == -1 ? nil : elem[:attack].to_i
450
+ @damage = elem[:damage].to_i == -1 ? nil : elem[:damage].to_i
451
+ @from_type = elem[:fromType] if elem[:fromType]
452
+ end
453
+ end
454
+
455
+
456
+
457
+ class Defenses
458
+ attr_reader :armor, :defense, :dodge, :parry, :block, :resilience
459
+
460
+ def initialize(elem)
461
+ @armor = Armor.new(elem%'armor')
462
+ @defense = Defense.new(elem%'defense')
463
+ @dodge = DodgeParryBlock.new(elem%'dodge')
464
+ @parry = DodgeParryBlock.new(elem%'parry')
465
+ @block = DodgeParryBlock.new(elem%'block')
466
+ @resilience = Resilience.new(elem%'resilience')
467
+ end
468
+ end
469
+
470
+ class Armor
471
+ attr_reader :base, :effective, :percent, :pet_bonus
472
+
473
+ def initialize(elem)
474
+ @base = elem[:base].to_i
475
+ @effective = elem[:effective].to_i
476
+ @percent = elem[:percent].to_f
477
+ @pet_bonus = elem[:petBonus].to_i == -1 ? nil : elem[:petBonus].to_i
478
+ end
479
+ end
480
+
481
+ class Defense
482
+ attr_reader :value, :increase_percent, :decrease_percent, :plus_defense, :rating
483
+
484
+ def initialize(elem)
485
+ @value = elem[:value].to_i
486
+ @increase_percent = elem[:increasePercent].to_f
487
+ @decrease_percent = elem[:decreasePercent].to_f
488
+ @plus_defense = elem[:plusDefense].to_i
489
+ @rating = elem[:rating].to_i
490
+ end
491
+ end
492
+
493
+ class DodgeParryBlock
494
+ attr_reader :percent, :increase_percent, :rating
495
+
496
+ def initialize(elem)
497
+ @percent = elem[:percent].to_f
498
+ @increase_percent = elem[:increasePercent].to_f
499
+ @rating = elem[:rating].to_i
500
+ end
501
+ end
502
+
503
+ class Resilience
504
+ attr_reader :damage_percent, :hit_percent, :value
505
+
506
+ def initialize(elem)
507
+ @damage_percent = elem[:damagePercent].to_f
508
+ @hit_percent = elem[:hitPercent].to_f
509
+ @value = elem[:value].to_f
510
+ end
511
+ end
512
+
513
+
514
+
515
+ class Resistance
516
+ attr_reader :value, :pet_bonus
517
+
518
+ def initialize(elem)
519
+ @value = elem[:value].to_i
520
+ @pet_bonus = elem[:petBonus].to_i == -1 ? nil : elem[:petBonus].to_i
521
+ end
522
+ end
523
+
524
+
525
+ # TODO: Array starting at 1? Not sure if this is such a hot idea
526
+ class TalentSpec
527
+ attr_reader :trees
528
+
529
+ def initialize(elem)
530
+ @trees = []
531
+ @trees[1] = elem[:treeOne].to_i
532
+ @trees[2] = elem[:treeTwo].to_i
533
+ @trees[3] = elem[:treeThree].to_i
534
+ end
535
+ end
536
+
537
+
538
+ # Player-versus-player data
539
+ class Pvp
540
+ attr_reader :lifetime_honorable_kills, :arena_currency
541
+
542
+ def initialize(elem)
543
+ @lifetime_honorable_kills = (elem%'lifetimehonorablekills')[:value].to_i
544
+ @arena_currency = (elem%'arenacurrency')[:value].to_i
545
+ end
546
+ end
547
+
548
+
549
+ # A buff
550
+ class Buff
551
+ attr_reader :name, :effect, :icon
552
+
553
+ def initialize(elem)
554
+ @name = elem[:name]
555
+ @effect = elem[:effect]
556
+ @icon = elem[:icon]
557
+ end
558
+ end
559
+
560
+
561
+ # A player's profession, players can only have 2 max
562
+ class Profession
563
+ attr_reader :key, :name, :max, :value
564
+
565
+ def initialize(elem)
566
+ @key = elem[:key]
567
+ @name = elem[:name]
568
+ @value = elem[:value].to_i
569
+ @max = elem[:max].to_i
570
+ end
571
+ end
572
+
573
+
574
+ # An item equipped to a player
575
+ class EquippedItem < Item
576
+ attr_reader :durability, :max_durability, #:id, :item_id, :icon,
577
+ :gems, :permanent_enchant,
578
+ :random_properties_id, :seed, :slot
579
+
580
+ def initialize(elem)
581
+ super(elem)
582
+ #@id = elem[:id].to_i
583
+ #@icon = elem[:icon]
584
+ @durability = elem[:durability].to_i
585
+ @max_durability = elem[:maxDurability].to_i
586
+ @gems = []
587
+ @gems[0] = elem[:gem0Id].to_i == 0 ? nil : elem[:gem0Id].to_i
588
+ @gems[1] = elem[:gem1Id].to_i == 0 ? nil : elem[:gem1Id].to_i
589
+ @gems[2] = elem[:gem2Id].to_i == 0 ? nil : elem[:gem2Id].to_i
590
+ @permanent_enchant = elem[:permanentEnchant].to_i
591
+ @random_properties_id = elem[:randomPropertiesId] == 0 ? nil : elem[:randomPropertiesId].to_i
592
+ @seed = elem[:seed].to_i # not sure if seed is so big it's overloading
593
+ @slot = elem[:slot].to_i
594
+ end
595
+ end
596
+
597
+
598
+
599
+ # class Damage
600
+ # :type, :min, :max, :speed, :dps
601
+ # end
602
+ #
603
+ # class ItemSource
604
+ # :area_id
605
+ # :area_name
606
+ # :creature_id
607
+ # :creature_name
608
+ # :difficulty
609
+ # :drop_rate
610
+ # :value
611
+ # end
612
+
613
+ # A player guild containing members
614
+ # note not all of these will be filled out, depending on how the data is found
615
+
616
+ class Guild
617
+ attr_reader :name, :url, :realm, :realm_url, :battle_group,
618
+ :roster_url, :stats_url, :stats_url_escape,
619
+ :faction, :faction_id,
620
+ :members, :member_count
621
+
622
+ def initialize(elem)
623
+ if (elem%'guildKey')
624
+ guild = (elem%'guildKey')
625
+ else
626
+ guild = elem
627
+ end
628
+
629
+ @name = guild[:name]
630
+ @name_url = guild[:nameUrl]
631
+ @url = guild[:url]
632
+ @realm = guild[:realm]
633
+ @realm_url = guild[:realmUrl]
634
+ @battle_group = guild[:battleGroup]
635
+ @faction = guild[:faction]
636
+ @faction_id = guild[:factionId]
637
+
638
+ # some shortened versions
639
+ if (elem%'guildInfo')
640
+ @member_count = (elem%'guildInfo'%'guild'%'members')[:memberCount].to_i || nil
641
+
642
+ @members = []
643
+ (elem%'guildInfo'%'guild'%'members'/:character).each do |char|
644
+ members << Character.new(char)
645
+ end
646
+ end
647
+ end
648
+ end
649
+
650
+
651
+ # class Guild
652
+ # attr_reader :name, :realm, :battle_group,
653
+ # :roster_url, :stats_url, :stats_url_escape,
654
+ # :members, :member_count
655
+ #
656
+ # def initialize(elem)
657
+ # @name = elem[:name]
658
+ # @realm = elem[:realm]
659
+ # @battle_group = elem[:battleGroup]
660
+ # @roster_url = elem[:rosterUrl]
661
+ # @stats_url = elem[:statsUrl]
662
+ # @stats_url_escape = elem[:statsUrlEscape]
663
+ # @member_count = (elem%'members')[:memberCount].to_i
664
+ #
665
+ # @members = []
666
+ # (elem%'members'/:character).each do |char|
667
+ # members << Character.new(char)
668
+ # end
669
+ # end
670
+ # end
671
+
672
+
673
+
674
+
675
+
676
+ # General skill category
677
+ # eg Weapon Skills, Languages
678
+ class SkillCategory
679
+ attr_reader :key, :name, :skills
680
+
681
+ def initialize(elem)
682
+ @key = elem[:key]
683
+ @name = elem[:name]
684
+
685
+ @skills = []
686
+ (elem/:skill).each do |skill|
687
+ @skills << Skill.new(skill)
688
+ end
689
+ end
690
+ end
691
+
692
+ #
693
+ # eg Daggers, Riding, Fishing
694
+ class Skill
695
+ attr_reader :key, :name, :value, :max
696
+
697
+ def initialize(elem)
698
+ @key = elem[:key]
699
+ @name = elem[:name]
700
+ @value = elem[:value]
701
+ @max = elem[:max]
702
+ end
703
+ end
704
+
705
+
706
+
707
+
708
+ # Larger group of factions
709
+ # eg Alliance, Shattrath City, Steamwheedle Cartel
710
+ # character-reputation.xml
711
+ class FactionCategory
712
+ attr_reader :key, :name, :factions
713
+
714
+ def initialize(elem)
715
+ @key = elem[:key]
716
+ @name = elem[:name]
717
+
718
+ @factions = []
719
+ (elem/:faction).each do |faction|
720
+ @factions << Faction.new(faction)
721
+ end
722
+ end
723
+ end
724
+
725
+ # Smaller NPC faction that is part of a FactionCategory
726
+ # eg Darnassus, Argent Dawn
727
+ class Faction
728
+ attr_reader :key, :name, :reputation
729
+
730
+ def initialize(elem)
731
+ @key = elem[:key]
732
+ @name = elem[:name]
733
+ @reputation = elem[:reputation].to_i
734
+ end
735
+ end
736
+
737
+
738
+
739
+
740
+ # Provides detailed item information
741
+ # Reflects information found within item-tooltip.xml
742
+ class ItemTooltip < Item
743
+ attr_reader :desc, :overall_quality_id, :bonding, :max_count, #:id, :name, :icon,
744
+ :class_id, :bonuses, :item_source,
745
+ :bonuses, :resistances,
746
+ :required_level,
747
+ :allowable_classes,
748
+ :armor, :durability,
749
+ :sockets, :socket_match_enchant,
750
+ :gem_properties
751
+ alias_method :description, :desc
752
+
753
+ def initialize(elem)
754
+ @id = (elem%'id').html.to_i
755
+ @name = (elem%'name').html
756
+ @icon = (elem%'icon').html
757
+ @desc = (elem%'desc').html if (elem%'desc')
758
+ @overall_quality_id = (elem%'overallQualityId').html.to_i
759
+ @bonding = (elem%'bonding').html.to_i
760
+ @stackable = (elem%'stackable').html.to_i if (elem%'stackable')
761
+ @max_count = (elem%'maxCount').html.to_i if (elem%'maxCount')
762
+ @class_id = (elem%'classId').html.to_i
763
+ @required_level = (elem%'requiredLevel').html.to_i if (elem%'requiredLevel')
764
+
765
+ @equipData = ItemEquipData.new(elem%'equipData')
766
+
767
+ # TODO: This appears to be a plain string at the moment
768
+ #<gemProperties>+26 Healing +9 Spell Damage and 2% Reduced Threat</gemProperties>
769
+ @gem_properties = (elem%'gemProperties').html if (elem%'gemProperties')
770
+
771
+ # not all items have damage data
772
+ @damage = ItemDamageData.new(elem%'damageData') if !(elem%'damageData').html.empty?
773
+
774
+
775
+ # TODO: Test socket data with a variety of items
776
+ # TODO: replace with socket Class?
777
+ if (elem%'socketData')
778
+ @sockets = []
779
+ (elem%'socketData'/:socket).each do |socket|
780
+ @sockets << socket[:color]
781
+ end
782
+
783
+ @socket_match_enchant = (elem%'socketData'%'socketMatchEnchant')
784
+ end
785
+
786
+
787
+ # When there is no data, stats are not present in @bonuses
788
+ # TODO: When there is no stats at all, @bonuses shouldn't be set
789
+ @bonuses = {}
790
+
791
+ bonus_stats = {
792
+ :strength => :bonusStrength,
793
+ :agility => :bonusAgility,
794
+ :stamina => :bonusStamina,
795
+ :intellect => :bonusIntellect,
796
+ :spirit => :bonusSpirit
797
+ }
798
+ bonus_stats.each do |stat, xml_elem|
799
+ @bonuses[stat] = test_stat(elem/xml_elem) if test_stat(elem/xml_elem)
800
+ end
801
+
802
+ # Resistances
803
+ @resistances = {}
804
+
805
+ resist_stats = {
806
+ :arcane => :arcaneResist,
807
+ :fire => :fireResist,
808
+ :frost => :frostResist,
809
+ :holy => :holyResist,
810
+ :nature => :natureResist,
811
+ :shadow => :shadowResist
812
+ }
813
+ resist_stats.each do |stat, xml_elem|
814
+ @resistances[stat] = test_stat(elem/xml_elem) if test_stat(elem/xml_elem)
815
+ end
816
+
817
+
818
+ #@bonuses[:strength] = (elem/:bonusStrength).html.to_i if (elem/:bonusStrength)
819
+
820
+ if (elem%'allowableClasses')
821
+ @allowable_classes = []
822
+ (elem%'allowableClasses'/:class).each do |klass|
823
+ @allowable_classes << klass.html
824
+ end
825
+ end
826
+
827
+ # NOTE not representing armor bonus
828
+ @armor = (elem%'armor').html.to_i if (elem%'armor')
829
+
830
+ # NOTE not representing max
831
+ @durability = (elem%'durability')[:current].to_i if (elem%'durability')
832
+
833
+ if (elem%'spellData')
834
+ @spells = []
835
+ (elem%'spellData'/:spell).each do |spell|
836
+ @spells << ItemSpell.new(spell)
837
+ end
838
+ end
839
+
840
+ @setData = ItemSetData.new(elem%'setData') if (elem%'setData')
841
+
842
+ # @item_sources = []
843
+ # (elem/:itemSource).each do |source|
844
+ # @item_sources << ItemSource.new(source)
845
+ # end
846
+ @item_source = ItemSource.new(elem%'itemSource') if (elem%'itemSource') # TODO: More than once source?
847
+ end
848
+
849
+ private
850
+ def test_stat(elem)
851
+ if elem
852
+ if !elem.html.empty?
853
+ return elem.html.to_i
854
+ end
855
+ end
856
+ return nil
857
+ end
858
+ end
859
+
860
+ class ItemEquipData
861
+ attr_reader :inventory_type, :subclass_name, :container_slots
862
+
863
+ def initialize(elem)
864
+ @inventory_type = (elem%'inventoryType').html.to_i
865
+ @subclass_name = (elem%'subclassName').html if (elem%'subclassName')
866
+ @container_slots = (elem%'containerSlots').html.to_i if (elem%'containerSlots') # for baggies
867
+ end
868
+ end
869
+
870
+ class ItemSetData
871
+ attr_reader :name, :items, :set_bonuses
872
+
873
+ def initialize(elem)
874
+ @name = elem[:name]
875
+
876
+ @items = []
877
+ (elem/:item).each do |item|
878
+ @items << item[:name]
879
+ end
880
+
881
+ @set_bonuses = []
882
+ (elem/:setBonus).each do |bonus|
883
+ @set_bonuses << ItemSetBonus.new(bonus)
884
+ end
885
+ end
886
+ end
887
+
888
+ class ItemSetBonus
889
+ attr_reader :threshold, :description
890
+ alias_method :desc, :description
891
+
892
+ def initialize(elem)
893
+ @threshold = elem[:threshold].to_i
894
+ @description = elem[:desc]
895
+ end
896
+ end
897
+
898
+ class ItemSpell
899
+ attr_reader :trigger, :description
900
+ alias_method :desc, :description
901
+
902
+ def initialize(elem)
903
+ @trigger = (elem%'trigger').html.to_i
904
+ @description = (elem%'desc').html
905
+ end
906
+ end
907
+
908
+
909
+ class ItemDamageData
910
+ attr_reader :type, :min, :max, :speed, :dps
911
+
912
+ def initialize(elem)
913
+ @type = (elem%'damage'%'type').html.to_i
914
+ @min = (elem%'damage'%'min').html.to_i
915
+ @max = (elem%'damage'%'max').html.to_i
916
+ @speed = (elem%'speed').html.to_i
917
+ @dps = (elem%'dps').html.to_f
918
+ end
919
+ end
920
+
921
+ class ItemSource
922
+ attr_reader :value,
923
+ :area_id, :area_name,
924
+ :creature_id, :creature_name,
925
+ :difficulty, :drop_rate
926
+
927
+ def initialize(elem)
928
+ @value = elem[:value]
929
+ @area_id = elem[:areaId].to_i if elem[:areaId]
930
+ @area_name = elem[:areaName] if elem[:areaName]
931
+ @creature_id = elem[:creatureId].to_i if elem[:creatureId]
932
+ @creature_name = elem[:creatureName] if elem[:creatureName]
933
+ @difficulty = elem[:difficulty] if elem[:difficulty]
934
+ @drop_rate = elem[:dropRate].to_i if elem[:dropRate]
935
+ @required_level = elem[:reqLvl].to_i if elem[:reqLvl]
936
+ end
937
+ end
938
+
939
+
940
+
941
+ # A really basic item type returned by searches
942
+ class SearchItem < Item
943
+ attr_reader :url, :rarity,
944
+ :source, :item_level, :relevance
945
+ alias_method :level, :item_level
946
+
947
+ def initialize(elem)
948
+ super(elem)
949
+ @rarity = elem[:rarity].to_i
950
+ @url = elem[:url]
951
+
952
+ @item_level = elem.at("filter[@name='itemLevel']")[:value].to_i
953
+ @source = elem.at("filter[@name='source']")[:value]
954
+ @relevance = elem.at("filter[@name='relevance']")[:value].to_i
955
+ end
956
+ end
957
+
958
+
959
+
960
+
961
+ # uses item-info.xml
962
+ class ItemInfo < Item
963
+ attr_reader :icon, :id, :level, :name, :quality, :type,
964
+ :cost, :disenchants, :disenchant_skill_rank, :vendors,
965
+ :plans_for
966
+
967
+ def initialize(elem)
968
+ @id = elem[:id].to_i
969
+ @level = elem[:level].to_i
970
+ @name = elem[:name]
971
+ @icon = elem[:icon]
972
+ @quality = elem[:quality].to_i
973
+ @type = elem[:type]
974
+
975
+ # Cost can be in gold, or tokens
976
+ @cost = ItemCost.new(elem%'cost') if (elem%'cost')
977
+
978
+
979
+
980
+ # is costs really an array?
981
+ #@costs = []
982
+ #(elem/:cost).each do |cost|
983
+ # @costs << ItemCost.new(cost)
984
+ #end
985
+
986
+ if (elem%'disenchantLoot')
987
+ @disenchant_skill_rank = (elem%'disenchantLoot')[:requiredSkillRank].to_i
988
+
989
+ @disenchant_items = []
990
+ (elem%'disenchantLoot'/:item).each do |item|
991
+ @disenchant_items << DisenchantItem.new(item)
992
+ end
993
+ end
994
+
995
+ if (elem%'objectiveOfQuests')
996
+ @objective_of_quests = []
997
+ (elem%'objectiveOfQuests'/:quest).each do |quest|
998
+ @objective_of_quests << ItemQuest.new(quest)
999
+ end
1000
+ end
1001
+
1002
+ if (elem%'rewardFromQuests')
1003
+ @reward_from_quests = []
1004
+ (elem%'rewardFromQuests'/:quest).each do |quest|
1005
+ @reward_from_quests << ItemQuest.new(quest)
1006
+ end
1007
+ end
1008
+
1009
+ if (elem%'vendors')
1010
+ @vendors = []
1011
+ (elem%'vendors'/:creature).each do |vendor|
1012
+ @vendors << ItemVendor.new(vendor)
1013
+ end
1014
+ end
1015
+
1016
+ if (elem%'dropCreatures')
1017
+ @drop_creatures = []
1018
+ (elem%'dropCreatures'/:creature).each do |creature|
1019
+ @drop_creatures << ItemDropCreature.new(creature)
1020
+ end
1021
+ end
1022
+
1023
+ if (elem%'plansFor')
1024
+ @plans_for = []
1025
+ (elem%'plansFor'/:spell).each do |plan|
1026
+ @plans_for << ItemPlansFor.new(plan)
1027
+ end
1028
+ end
1029
+
1030
+ if (elem%'createdBy')
1031
+ @created_by = []
1032
+ (elem%'createdBy'/:spell).each do |c|
1033
+ @created_by << ItemCreatedBy.new(c)
1034
+ end
1035
+ end
1036
+ end
1037
+ end
1038
+
1039
+ # <rewardFromQuests>
1040
+ # <quest name="Justice Dispensed" level="39" reqMinLevel="30" id="11206" area="Dustwallow Marsh" suggestedPartySize="0"></quest>
1041
+ # <quest name="Peace at Last" level="39" reqMinLevel="30" id="11152" area="Dustwallow Marsh" suggestedPartySize="0"></quest>
1042
+ # </rewardFromQuests>
1043
+ # TODO: Rename
1044
+ class ItemQuest
1045
+ attr_reader :name, :id, :level, :min_level, :area, :suggested_party_size
1046
+
1047
+ def initialize(elem)
1048
+ @name = elem[:name]
1049
+ @id = elem[:id].to_i
1050
+ @level = elem[:level].to_i
1051
+ @min_level = elem[:min_level].to_i
1052
+ @area = elem[:area]
1053
+ @suggested_party_size = elem[:suggested_party_size].to_i
1054
+ end
1055
+ end
1056
+
1057
+
1058
+
1059
+ # Creatures that drop the item
1060
+ # <creature name="Giant Marsh Frog" minLevel="1" type="Critter" maxLevel="1" dropRate="6" id="23979" classification="0" area="Dustwallow Marsh"></creature>
1061
+ # <creature name="Nalorakk" minLevel="73" title="Bear Avatar" url="fl[source]=dungeon&amp;fl[difficulty]=normal&amp;fl[boss]=23576" type="Humanoid" maxLevel="73" dropRate="2" id="23576" classification="3" areaUrl="fl[source]=dungeon&amp;fl[boss]=all&amp;fl[difficulty]=normal&amp;fl[dungeon]=3805" area="Zul'Aman"></creature>
1062
+ class ItemDropCreature
1063
+ attr_reader :name, :id, :type, :min_level, :max_level, :drop_rate, :classification, :area
1064
+
1065
+ def initialize(elem)
1066
+ @name = elem[:name]
1067
+ @id = elem[:id].to_i
1068
+ @min_level = elem[:minLevel].to_i
1069
+ @max_level = elem[:maxLevel].to_i
1070
+ @drop_rate = elem[:dropRate].to_i
1071
+ @classification = elem[:classification].to_i
1072
+ @area = elem[:area]
1073
+
1074
+ # optional boss stuff
1075
+ @title = elem[:title] if elem[:title] # TODO: not nil when no property?
1076
+ @url = elem[:url] if elem[:url]
1077
+ @type = elem[:type] if elem[:type] # Humanoid etc.
1078
+ @area_url = elem[:areaUrl] if elem[:areaUrl]
1079
+ end
1080
+ end
1081
+
1082
+
1083
+ # <cost sellPrice="280" buyPrice="5600"></cost>
1084
+ # <cost>
1085
+ # <token icon="spell_holy_championsbond" id="29434" count="60"></token>
1086
+ # </cost>
1087
+ class ItemCost
1088
+ attr_reader :buy_price, :sell_price, :tokens
1089
+
1090
+ def initialize(elem)
1091
+ @buy_price = elem[:buyPrice].to_i if elem[:buyPrice]
1092
+ @sell_price = elem[:sellPrice].to_i if elem[:sellPrice]
1093
+
1094
+ if (elem%'token')
1095
+ @tokens = []
1096
+ (elem/:token).each do |token|
1097
+ @tokens << ItemCostToken.new(token)
1098
+ end
1099
+ end
1100
+ end
1101
+ end
1102
+
1103
+ # <token icon="spell_holy_championsbond" id="29434" count="60"></token>
1104
+ class ItemCostToken
1105
+ attr_reader :id, :icon, :count
1106
+
1107
+ def initialize(elem)
1108
+ @id = elem[:id].to_i
1109
+ @icon = elem[:icon]
1110
+ @count = elem[:count].to_i
1111
+ end
1112
+ end
1113
+
1114
+ # <item name="Void Crystal" minCount="1" maxCount="2" icon="inv_enchant_voidcrystal" type="Enchanting" level="70" dropRate="6" id="22450" quality="4"></item>
1115
+ class DisenchantItem
1116
+ attr_reader :name, :id, :icon, :level, :type, :drop_rate, :min_count, :max_count, :quality
1117
+
1118
+ def initialize(elem)
1119
+ @name = elem[:name]
1120
+ @id = elem[:id].to_i
1121
+ @icon = elem[:icon]
1122
+ @level = elem[:level].to_i
1123
+ @type = elem[:type]
1124
+ @drop_rate = elem[:dropRate].to_i
1125
+ @min_count = elem[:minCount].to_i
1126
+ @max_count = elem[:maxCount].to_i
1127
+ @quality = elem[:quality].to_i
1128
+ end
1129
+ end
1130
+
1131
+ class ItemVendor
1132
+ attr_reader :id, :name, :title, :type,
1133
+ :area, :classification, :max_level, :min_level
1134
+
1135
+ def initialize(elem)
1136
+ @id = elem[:id].to_i
1137
+ @name = elem[:name]
1138
+ @title = elem[:title]
1139
+ @type = elem[:type]
1140
+ @area = elem[:area]
1141
+ @classification = elem[:classification].to_i
1142
+ @max_level = elem[:maxLevel].to_i
1143
+ @min_level = elem[:minLevel].to_i
1144
+ end
1145
+ end
1146
+
1147
+
1148
+
1149
+
1150
+ # TODO rename
1151
+ # There is some sort of opposite relationship between PlansFor and CreatedBy
1152
+ class ItemCreation
1153
+ attr_reader :name, :id, :icon,
1154
+ :item, :reagents
1155
+
1156
+ def initialize(elem)
1157
+ @name = elem[:name]
1158
+ @id = elem[:id].to_i
1159
+ @icon = elem[:icon]
1160
+
1161
+ # not all items have reagents, some are just spells
1162
+ if (elem%'reagent')
1163
+ @reagents = []
1164
+ (elem/:reagent).each do |reagent|
1165
+ @reagents << Reagent.new(reagent)
1166
+ end
1167
+ end
1168
+ end
1169
+ end
1170
+
1171
+
1172
+ # <plansFor>
1173
+ # <spell name="Shadowprowler's Chestguard" icon="trade_leatherworking" id="42731">
1174
+ # <item name="Shadowprowler's Chestguard" icon="inv_chest_plate11" type="Leather" level="105" id="33204" quality="4"></item>
1175
+ # <reagent name="Heavy Knothide Leather" icon="inv_misc_leatherscrap_11" id="23793" count="10"></reagent>
1176
+ # <reagent name="Bolt of Soulcloth" icon="inv_fabric_soulcloth_bolt" id="21844" count="16"></reagent>
1177
+ # <reagent name="Primal Earth" icon="inv_elemental_primal_earth" id="22452" count="12"></reagent>
1178
+ # <reagent name="Primal Shadow" icon="inv_elemental_primal_shadow" id="22456" count="12"></reagent>
1179
+ # <reagent name="Primal Nether" icon="inv_elemental_primal_nether" id="23572" count="2"></reagent>
1180
+ # </spell>
1181
+ # </plansFor>
1182
+ class ItemPlansFor < ItemCreation
1183
+ def initialize(elem)
1184
+ super(elem)
1185
+ # TODO: Multiple items?
1186
+ @item = CreatedItem.new(elem%'item') if (elem%'item')
1187
+ end
1188
+ end
1189
+
1190
+ # <createdBy>
1191
+ # <spell name="Bracing Earthstorm Diamond" icon="temp" id="32867">
1192
+ # <item requiredSkill="Jewelcrafting" name="Design: Bracing Earthstorm Diamond" icon="inv_scroll_03" type="Jewelcrafting" level="73" id="25903" requiredSkillRank="365" quality="1"></item>
1193
+ # <reagent name="Earthstorm Diamond" icon="inv_misc_gem_diamond_04" id="25867" count="1"></reagent>
1194
+ # </spell>
1195
+ # </createdBy>
1196
+ class ItemCreatedBy < ItemCreation
1197
+ def initialize(elem)
1198
+ super(elem)
1199
+ # TODO: Multiple items?
1200
+ @item = PlanItem.new(elem%'item') if (elem%'item')
1201
+ end
1202
+ end
1203
+
1204
+ # TODO: Might be better to reuse an existing Item class?
1205
+ # <item name="Shadowprowler's Chestguard" icon="inv_chest_plate11" type="Leather" level="105" id="33204" quality="4"></item>
1206
+ class CreatedItem < Item
1207
+ attr_reader :type, :level, :quality
1208
+
1209
+ def initialize(elem)
1210
+ super(elem)
1211
+ @type = elem[:type]
1212
+ @level = elem[:level].to_i
1213
+ @quality = elem[:quality].to_i
1214
+ end
1215
+ end
1216
+
1217
+ # TODO: Might be better to reuse an existing Item class?
1218
+ # <item requiredSkill="Jewelcrafting" name="Design: Bracing Earthstorm Diamond" icon="inv_scroll_03" type="Jewelcrafting" level="73" id="25903" requiredSkillRank="365" quality="1"></item>
1219
+ class PlanItem < Item
1220
+ attr_reader :required_skill, :type, :required_skill_rank, :level, :quality
1221
+
1222
+ def initialize(elem)
1223
+ super(elem)
1224
+ @type = elem[:type]
1225
+ @level = elem[:level].to_i
1226
+ @quality = elem[:quality].to_i
1227
+ @required_skill = elem[:requiredSkill]
1228
+ @required_skill_rank = elem[:requiredSkillRank].to_i
1229
+ end
1230
+ end
1231
+
1232
+ class Reagent
1233
+ attr_reader :id, :name, :icon, :count
1234
+
1235
+ def initialize(elem)
1236
+ @id = elem[:id].to_i
1237
+ @name = elem[:name]
1238
+ @icon = elem[:icon]
1239
+ @count = elem[:count].to_i
1240
+ end
1241
+ end
1242
+
1243
+
1244
+
1245
+ # A group of individuals
1246
+ # Note that search results don't contain the members
1247
+ class ArenaTeam
1248
+ attr_reader :name, :size, :battle_group, :faction, :faction_id, :realm, :realm_url,
1249
+ :games_played, :games_won, :ranking, :rating,
1250
+ :season_games_played, :season_games_won, :last_season_ranking,
1251
+ :relevance, :url, :url_escape,
1252
+ :characters, # can be blank on search results
1253
+ :emblem
1254
+
1255
+ def initialize(elem)
1256
+ @name = elem[:name]
1257
+ @size = elem[:size].to_i
1258
+ @battle_group = elem[:battleGroup]
1259
+ @faction = elem[:faction]
1260
+ @faction_id = elem[:factionId].to_i
1261
+ @realm = elem[:realm]
1262
+ @realm_url = elem[:realmUrl]
1263
+
1264
+ @games_played = elem[:gamesPlayed].to_i
1265
+ @games_won = elem[:gamesWon].to_i
1266
+ @ranking = elem[:ranking].to_i
1267
+ @rating = elem[:rating].to_i
1268
+
1269
+ @season_games_played = elem[:seasonGamesPlayed].to_i
1270
+ @season_games_won = elem[:seasonGamesWon].to_i
1271
+ @last_season_ranking = elem[:lastSeasonRanking].to_i
1272
+
1273
+ @relevance = elem[:relevance].to_i
1274
+ @url = elem[:url]
1275
+
1276
+ @emblem = ArenaTeamEmblem.new(elem%'emblem')
1277
+
1278
+ @members = []
1279
+ (elem%'members'/:character).each do |character|
1280
+ @members << Character.new(character)
1281
+ end
1282
+ end
1283
+ end
1284
+
1285
+ class ArenaTeamEmblem
1286
+ attr_reader :background, :border_color, :border_style, :icon_colour, :icon_style
1287
+
1288
+ def initialize(elem)
1289
+ @background = elem[:background]
1290
+ @border_color = elem[:borderColor]
1291
+ @border_style = elem[:borderStyle].to_i
1292
+ @icon_color = elem[:iconColor]
1293
+ @icon_style = elem[:iconStyle].to_i
1294
+ end
1295
+ end
1296
+
1297
+
1298
+ end
1299
+ end