pwood-wowr 0.5.1

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.
@@ -0,0 +1,577 @@
1
+ # TODO: Item sources - Vendors
2
+ # sourceType.vendor
3
+ # sourceType.questReward
4
+ # sourceType.createdBySpell
5
+
6
+ $:.unshift(File.dirname(__FILE__)) unless $:.include?(File.dirname(__FILE__)) || $:.include?(File.expand_path(File.dirname(__FILE__)))
7
+ $LOAD_PATH.unshift(File.dirname(__FILE__))
8
+
9
+ require 'general.rb'
10
+
11
+ module Wowr
12
+ module Classes
13
+
14
+ # Most basic
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
+ # TODO: At the moment needs a reference to the API in order to get the base URL for icons
19
+ # TODO: Make extend Icon class
20
+ class Item
21
+ attr_reader :id, :name, :icon_base
22
+ alias_method :item_id, :id
23
+ alias_method :to_s, :name
24
+ alias_method :to_i, :id
25
+
26
+ @@icon_url_base = 'images/icons/'
27
+ @@icon_url_base_tw = 'wow-icons/_images/'
28
+ @@icon_sizes = {:large => ['64x64', 'jpg'], :medium => ['43x43', 'png'], :small => ['21x21', 'png']}
29
+
30
+ def initialize(elem, api = nil)
31
+ @api = api
32
+
33
+ @id = elem[:id].to_i
34
+ @name = elem[:name]
35
+ @icon_base = elem[:icon]
36
+ end
37
+
38
+ def icon(size = :medium)
39
+ if !@@icon_sizes.include?(size)
40
+ raise Wowr::Exceptions::InvalidIconSize.new(@@icon_sizes)
41
+ end
42
+
43
+ if @api
44
+ base = @api.base_url
45
+ else
46
+ base = 'http://www.wowarmory.com/'
47
+ end
48
+
49
+ if @api && @api.locale == "tw"
50
+ url_base = @@icon_url_base_tw
51
+ else
52
+ url_base = @@icon_url_base
53
+ end
54
+
55
+ # http://www.wowarmory.com/images/icons/64x64/blahblah.jpg
56
+ return base + url_base + @@icon_sizes[size][0] + '/' + @icon_base + '.' + @@icon_sizes[size][1]
57
+ end
58
+ end
59
+
60
+ # Full data from item-info and item-tooltip
61
+ class FullItem < Item
62
+
63
+ def initialize(info, tooltip, api = nil)
64
+ super(info, api)
65
+ @info = ItemInfo.new(info, api)
66
+ @tooltip = ItemTooltip.new(tooltip, api)
67
+ end
68
+
69
+ def method_missing(m, *args)
70
+ begin
71
+ return @info.send(m, *args)
72
+ rescue NoMethodError => e
73
+ begin
74
+ return @tooltip.send(m, *args)
75
+ rescue
76
+ raise NoMethodError.new("undefined method '#{m}' for #{self.class}")
77
+ end
78
+ end
79
+ end
80
+ end
81
+
82
+
83
+
84
+
85
+ # uses item-info.xml
86
+ class ItemInfo < Item
87
+ attr_reader :level, :quality, :type,
88
+ :cost, :disenchant_items, :disenchant_skill_rank, :vendors,
89
+ :objective_of_quests,
90
+ :reward_from_quests,
91
+ :drop_creatures,
92
+ :plans_for, :created_by
93
+
94
+ alias_method :disenchants, :disenchant_items
95
+
96
+ def initialize(elem, api = nil)
97
+ super(elem, api)
98
+
99
+ @level = elem[:level].to_i
100
+ @quality = elem[:quality].to_i
101
+ @type = elem[:type]
102
+
103
+ # Cost can be in gold, or tokens
104
+ @cost = ItemCost.new(elem%'cost') if (elem%'cost')
105
+
106
+
107
+ # is costs really an array?
108
+ #@costs = []
109
+ #(elem/:cost).each do |cost|
110
+ # @costs << ItemCost.new(cost)
111
+ #end
112
+
113
+ etc = [
114
+ # xml element name, member variable item list class name, requires api link
115
+ ['disenchantLoot', '@disenchant_items', 'item', DisenchantItem, true],
116
+ ['objectiveOfQuests', '@objective_of_quests', 'quest', ItemQuest, false],
117
+ ['rewardFromQuests', '@reward_from_quests', 'quest', ItemQuest, false],
118
+ ['vendors', '@vendors', 'creature', ItemVendor, false],
119
+ ['dropCreatures', '@drop_creatures', 'creature', ItemDropCreature, false],
120
+ ['plansFor', '@plans_for', 'spell', ItemPlansFor, true],
121
+ ['createdBy', '@created_by', 'spell', ItemCreatedBy, true],
122
+ ]
123
+
124
+ etc.each do |b|
125
+ ele = b[0]
126
+ var = b[1]
127
+ list = b[2]
128
+ my_class = b[3]
129
+ requires_api = b[4]
130
+
131
+ if elem%ele
132
+ tmp_arr = []
133
+ (elem%ele/list).each do |x|
134
+ if requires_api
135
+ tmp_arr << my_class.new(x, api)
136
+ else
137
+ tmp_arr << my_class.new(x)
138
+ end
139
+ end
140
+ self.instance_variable_set(var, tmp_arr)
141
+ end
142
+ end
143
+
144
+ # Rest of disenchant contents is done in the method above
145
+ if (elem%'disenchantLoot')
146
+ @disenchant_skill_rank = (elem%'disenchantLoot')[:requiredSkillRank].to_i
147
+ end
148
+ end
149
+ end
150
+
151
+
152
+
153
+ # Provides detailed item information
154
+ # Note that the item-tooltip.xml just returns an empty document when the item
155
+ # can't be found.
156
+ class ItemTooltip < Item
157
+ attr_reader :desc, :overall_quality_id, :bonding, :max_count, #:id, :name, :icon,
158
+ :class_id, :bonuses, :item_source,
159
+ :resistances, :required_level,
160
+ :allowable_classes, :armor, :durability,
161
+ :sockets, :socket_match_enchant,
162
+ :gem_properties, :equip_data
163
+ alias_method :description, :desc
164
+
165
+ def initialize(elem, api = nil)
166
+ super(elem, api)
167
+ @id = (elem%'id').html.to_i
168
+ @name = (elem%'name').html
169
+ @icon_base = (elem%'icon').html
170
+ @desc = (elem%'desc').html if (elem%'desc')
171
+ @overall_quality_id = (elem%'overallQualityId').html.to_i
172
+ @bonding = (elem%'bonding').html.to_i
173
+ @stackable = (elem%'stackable').html.to_i if (elem%'stackable')
174
+ @max_count = (elem%'maxCount').html.to_i if (elem%'maxCount')
175
+ @class_id = (elem%'classId').html.to_i
176
+ @required_level = (elem%'requiredLevel').html.to_i if (elem%'requiredLevel')
177
+
178
+ @equip_data = ItemEquipData.new(elem%'equipData')
179
+
180
+ # TODO: This appears to be a plain string at the moment
181
+ #<gemProperties>+26 Healing +9 Spell Damage and 2% Reduced Threat</gemProperties>
182
+ @gem_properties = (elem%'gemProperties').html if (elem%'gemProperties')
183
+
184
+ # not all items have damage data
185
+ @damage = ItemDamageData.new(elem%'damageData') unless !(elem%'damageData') || (elem%'damageData').empty?
186
+
187
+
188
+ # TODO: Test socket data with a variety of items
189
+ # TODO: replace with socket Class?
190
+ if (elem%'socketData')
191
+ @sockets = []
192
+ (elem%'socketData'/:socket).each do |socket|
193
+ @sockets << socket[:color]
194
+ end
195
+
196
+ @socket_match_enchant = (elem%'socketData'%'socketMatchEnchant')
197
+ end
198
+
199
+
200
+ # When there is no data, stats are not present in @bonuses
201
+ # TODO: When there is no stats at all, @bonuses shouldn't be set
202
+ @bonuses = {}
203
+
204
+ bonus_stats = {
205
+ :strength => :bonusStrength,
206
+ :agility => :bonusAgility,
207
+ :stamina => :bonusStamina,
208
+ :intellect => :bonusIntellect,
209
+ :spirit => :bonusSpirit
210
+ }
211
+ bonus_stats.each do |stat, xml_elem|
212
+ @bonuses[stat] = test_stat(elem/xml_elem) if test_stat(elem/xml_elem)
213
+ end
214
+
215
+ # Resistances
216
+ @resistances = {}
217
+
218
+ resist_stats = {
219
+ :arcane => :arcaneResist,
220
+ :fire => :fireResist,
221
+ :frost => :frostResist,
222
+ :holy => :holyResist,
223
+ :nature => :natureResist,
224
+ :shadow => :shadowResist
225
+ }
226
+ resist_stats.each do |stat, xml_elem|
227
+ @resistances[stat] = test_stat(elem/xml_elem) if test_stat(elem/xml_elem)
228
+ end
229
+
230
+
231
+ if (elem%'allowableClasses')
232
+ @allowable_classes = []
233
+ (elem%'allowableClasses'/:class).each do |klass|
234
+ @allowable_classes << klass.html
235
+ end
236
+ end
237
+
238
+ # NOTE not representing armor bonus
239
+ @armor = (elem%'armor').html.to_i if (elem%'armor')
240
+
241
+ # NOTE not representing max
242
+ @durability = (elem%'durability')[:current].to_i if (elem%'durability')
243
+
244
+ if (elem%'spellData')
245
+ @spells = []
246
+ (elem%'spellData'/:spell).each do |spell|
247
+ @spells << ItemSpell.new(spell)
248
+ end
249
+ end
250
+
251
+ @setData = ItemSetData.new(elem%'setData') if (elem%'setData')
252
+
253
+ # @item_sources = []
254
+ # (elem/:itemSource).each do |source|
255
+ # @item_sources << ItemSource.new(source)
256
+ # end
257
+ @item_source = ItemSource.new(elem%'itemSource') if (elem%'itemSource') # TODO: More than once source?
258
+ end
259
+
260
+ private
261
+ def test_stat(elem)
262
+ if elem
263
+ if !elem.html.empty?
264
+ return elem.html.to_i
265
+ end
266
+ end
267
+ return nil
268
+ end
269
+ end
270
+
271
+ class ItemEquipData
272
+ attr_reader :inventory_type, :subclass_name, :container_slots
273
+
274
+ def initialize(elem)
275
+ @inventory_type = (elem%'inventoryType').html.to_i
276
+ @subclass_name = (elem%'subclassName').html if (elem%'subclassName')
277
+ @container_slots = (elem%'containerSlots').html.to_i if (elem%'containerSlots') # for baggies
278
+ end
279
+ end
280
+
281
+ class ItemSetData
282
+ attr_reader :name, :items, :set_bonuses
283
+ alias_method :to_s, :name
284
+
285
+ def initialize(elem)
286
+ @name = elem[:name]
287
+
288
+ @items = []
289
+ (elem/:item).each do |item|
290
+ @items << item[:name]
291
+ end
292
+
293
+ @set_bonuses = []
294
+ (elem/:setBonus).each do |bonus|
295
+ @set_bonuses << ItemSetBonus.new(bonus)
296
+ end
297
+ end
298
+ end
299
+
300
+ class ItemSetBonus
301
+ attr_reader :threshold, :description
302
+ alias_method :desc, :description
303
+ alias_method :to_s, :description
304
+
305
+ def initialize(elem)
306
+ @threshold = elem[:threshold].to_i
307
+ @description = elem[:desc]
308
+ end
309
+ end
310
+
311
+ class ItemSpell
312
+ attr_reader :trigger, :description
313
+ alias_method :desc, :description
314
+ alias_method :to_s, :description
315
+
316
+ def initialize(elem)
317
+ @trigger = (elem%'trigger').html.to_i
318
+ @description = (elem%'desc').html
319
+ end
320
+ end
321
+
322
+ class ItemDamageData
323
+ attr_reader :type, :min, :max, :speed, :dps
324
+
325
+ def initialize(elem)
326
+ @type = (elem%'damage'%'type').html.to_i
327
+ @min = (elem%'damage'%'min').html.to_i
328
+ @max = (elem%'damage'%'max').html.to_i
329
+ @speed = (elem%'speed').html.to_i if (elem%'speed')
330
+ @dps = (elem%'dps').html.to_f if (elem%'dps')
331
+ end
332
+ end
333
+
334
+ class ItemSource
335
+ attr_reader :value,
336
+ :area_id, :area_name,
337
+ :creature_id, :creature_name,
338
+ :difficulty, :drop_rate
339
+
340
+ def initialize(elem)
341
+ @value = elem[:value]
342
+ @area_id = elem[:areaId].to_i if elem[:areaId]
343
+ @area_name = elem[:areaName] if elem[:areaName]
344
+ @creature_id = elem[:creatureId].to_i if elem[:creatureId]
345
+ @creature_name = elem[:creatureName] if elem[:creatureName]
346
+ @difficulty = elem[:difficulty] if elem[:difficulty]
347
+ @drop_rate = elem[:dropRate].to_i if elem[:dropRate]
348
+ @required_level = elem[:reqLvl].to_i if elem[:reqLvl]
349
+ end
350
+ end
351
+
352
+
353
+
354
+ # A really basic item type returned by searches
355
+ class SearchItem < Item
356
+ attr_reader :url, :rarity,
357
+ :source, :item_level, :relevance
358
+ alias_method :level, :item_level
359
+
360
+ def initialize(elem, api = nil)
361
+ super(elem, api)
362
+ @rarity = elem[:rarity].to_i
363
+ @url = elem[:url]
364
+
365
+ @item_level = elem.at("filter[@name='itemLevel']")[:value].to_i
366
+ @source = elem.at("filter[@name='source']")[:value]
367
+ @relevance = elem.at("filter[@name='relevance']")[:value].to_i
368
+ end
369
+ end
370
+
371
+
372
+
373
+
374
+ # <rewardFromQuests>
375
+ # <quest name="Justice Dispensed" level="39" reqMinLevel="30" id="11206" area="Dustwallow Marsh" suggestedPartySize="0"></quest>
376
+ # <quest name="Peace at Last" level="39" reqMinLevel="30" id="11152" area="Dustwallow Marsh" suggestedPartySize="0"></quest>
377
+ # </rewardFromQuests>
378
+ # TODO: Rename
379
+ class ItemQuest
380
+ attr_reader :name, :id, :level, :min_level, :area, :suggested_party_size
381
+
382
+ def initialize(elem)
383
+ @name = elem[:name]
384
+ @id = elem[:id].to_i
385
+ @level = elem[:level].to_i
386
+ @min_level = elem[:min_level].to_i
387
+ @area = elem[:area]
388
+ @suggested_party_size = elem[:suggested_party_size].to_i
389
+ end
390
+ end
391
+
392
+
393
+
394
+ # Creatures that drop the item
395
+ # <creature name="Giant Marsh Frog" minLevel="1" type="Critter" maxLevel="1" dropRate="6" id="23979" classification="0" area="Dustwallow Marsh"></creature>
396
+ # <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>
397
+ class ItemDropCreature
398
+ attr_reader :name, :id, :type, :min_level, :max_level, :drop_rate, :classification, :area
399
+
400
+ def initialize(elem)
401
+ @name = elem[:name]
402
+ @id = elem[:id].to_i
403
+ @min_level = elem[:minLevel].to_i
404
+ @max_level = elem[:maxLevel].to_i
405
+ @drop_rate = elem[:dropRate].to_i
406
+ @classification = elem[:classification].to_i
407
+ @area = elem[:area]
408
+
409
+ # optional boss stuff
410
+ @title = elem[:title] if elem[:title] # TODO: not nil when no property?
411
+ @url = elem[:url] if elem[:url]
412
+ @type = elem[:type] if elem[:type] # Humanoid etc.
413
+ @area_url = elem[:areaUrl] if elem[:areaUrl]
414
+ end
415
+ end
416
+
417
+ # Cost can be gold or a set of required tokens
418
+ # See ItemCostToken
419
+ # <cost sellPrice="280" buyPrice="5600"></cost>
420
+ # <cost>
421
+ # <token icon="spell_holy_championsbond" id="29434" count="60"></token>
422
+ # </cost>
423
+ class ItemCost
424
+ attr_reader :buy_price, :sell_price, :tokens
425
+
426
+ def initialize(elem)
427
+ @buy_price = Money.new(elem[:buyPrice].to_i) if elem[:buyPrice]
428
+ @sell_price = Money.new(elem[:sellPrice].to_i) if elem[:sellPrice]
429
+
430
+ if (elem%'token')
431
+ @tokens = []
432
+ (elem/:token).each do |token|
433
+ @tokens << ItemCostToken.new(token)
434
+ end
435
+ end
436
+ end
437
+ end
438
+
439
+ # <token icon="spell_holy_championsbond" id="29434" count="60"></token>
440
+ class ItemCostToken < Item
441
+ attr_reader :count
442
+
443
+ def initialize(elem, api = nil)
444
+ super(elem)
445
+ # @id = elem[:id].to_i
446
+ # @icon_bse = elem[:icon]
447
+ @count = elem[:count].to_i
448
+ end
449
+ end
450
+
451
+ # <item name="Void Crystal" minCount="1" maxCount="2" icon="inv_enchant_voidcrystal" type="Enchanting" level="70" dropRate="6" id="22450" quality="4"></item>
452
+ class DisenchantItem < Item
453
+ attr_reader :level, :type, :drop_rate, :min_count, :max_count, :quality
454
+ # :name, :id, :icon,
455
+
456
+ def initialize(elem, api = nil)
457
+ super(elem, api)
458
+ # @name = elem[:name]
459
+ # @id = elem[:id].to_i
460
+ # @icon = elem[:icon]
461
+ @level = elem[:level].to_i
462
+ @type = elem[:type]
463
+ @drop_rate = elem[:dropRate].to_i
464
+ @min_count = elem[:minCount].to_i
465
+ @max_count = elem[:maxCount].to_i
466
+ @quality = elem[:quality].to_i
467
+ end
468
+ end
469
+
470
+
471
+ class ItemVendor
472
+ attr_reader :id, :name, :title, :type,
473
+ :area, :classification, :max_level, :min_level
474
+ alias_method :to_s, :name
475
+ alias_method :to_i, :id
476
+
477
+ def initialize(elem)
478
+ @id = elem[:id].to_i
479
+ @name = elem[:name]
480
+ @title = elem[:title]
481
+ @type = elem[:type]
482
+ @area = elem[:area]
483
+ @classification = elem[:classification].to_i
484
+ @max_level = elem[:maxLevel].to_i
485
+ @min_level = elem[:minLevel].to_i
486
+ end
487
+ end
488
+
489
+
490
+
491
+ # TODO rename
492
+ # There is some sort of opposite relationship between PlansFor and CreatedBy
493
+ class ItemCreation < Item
494
+ attr_reader :item, :reagents
495
+
496
+ def initialize(elem, api = nil)
497
+ super(elem, api)
498
+
499
+ if (elem%'reagent')
500
+ @reagents = []
501
+ (elem/:reagent).each do |reagent|
502
+ @reagents << Reagent.new(reagent, api)
503
+ end
504
+ end
505
+ end
506
+ end
507
+
508
+ # (fold)
509
+ # <plansFor>
510
+ # <spell name="Shadowprowler's Chestguard" icon="trade_leatherworking" id="42731">
511
+ # <item name="Shadowprowler's Chestguard" icon="inv_chest_plate11" type="Leather" level="105" id="33204" quality="4"></item>
512
+ # <reagent name="Heavy Knothide Leather" icon="inv_misc_leatherscrap_11" id="23793" count="10"></reagent>
513
+ # </spell>
514
+ # </plansFor>
515
+ # (end)
516
+ class ItemPlansFor < ItemCreation
517
+ def initialize(elem, api = nil)
518
+ super(elem, api)
519
+ # TODO: Multiple items?
520
+ @item = CreatedItem.new(elem%'item') if (elem%'item')
521
+ end
522
+ end
523
+
524
+ # <createdBy>
525
+ # <spell name="Bracing Earthstorm Diamond" icon="temp" id="32867">
526
+ # <item requiredSkill="Jewelcrafting" name="Design: Bracing Earthstorm Diamond" icon="inv_scroll_03" type="Jewelcrafting" level="73" id="25903" requiredSkillRank="365" quality="1"></item>
527
+ # <reagent name="Earthstorm Diamond" icon="inv_misc_gem_diamond_04" id="25867" count="1"></reagent>
528
+ # </spell>
529
+ # </createdBy>
530
+ class ItemCreatedBy < ItemCreation
531
+ def initialize(elem, api = nil)
532
+ super(elem, api)
533
+ # TODO: Multiple items?
534
+ @item = PlanItem.new(elem%'item') if (elem%'item')
535
+ end
536
+ end
537
+
538
+
539
+ # <item name="Shadowprowler's Chestguard" icon="inv_chest_plate11" type="Leather" level="105" id="33204" quality="4"></item>
540
+ class CreatedItem < Item
541
+ attr_reader :type, :level, :quality
542
+
543
+ def initialize(elem, api = nil)
544
+ super(elem, api)
545
+ @type = elem[:type]
546
+ @level = elem[:level].to_i
547
+ @quality = elem[:quality].to_i
548
+ end
549
+ end
550
+
551
+ # <item requiredSkill="Jewelcrafting" name="Design: Bracing Earthstorm Diamond" icon="inv_scroll_03" type="Jewelcrafting" level="73" id="25903" requiredSkillRank="365" quality="1"></item>
552
+ class PlanItem < Item
553
+ attr_reader :required_skill, :type, :required_skill_rank, :level, :quality
554
+
555
+ def initialize(elem, api = nil)
556
+ super(elem, api)
557
+ @type = elem[:type]
558
+ @level = elem[:level].to_i
559
+ @quality = elem[:quality].to_i
560
+ @required_skill = elem[:requiredSkill]
561
+ @required_skill_rank = elem[:requiredSkillRank].to_i
562
+ end
563
+ end
564
+
565
+ class Reagent < Item
566
+ attr_reader :count
567
+
568
+ def initialize(elem, api = nil)
569
+ super(elem, api)
570
+ # @id = elem[:id].to_i
571
+ # @name = elem[:name]
572
+ # @icon = elem[:icon]
573
+ @count = elem[:count].to_i
574
+ end
575
+ end
576
+ end
577
+ end