pwood-wowr 0.5.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -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