wowr 0.1.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/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