WoW Model Viewer
Your premiere tool for viewing, equipping and animating World of Warcraft models.
Loading...
Searching...
No Matches
WoWItem.cpp
Go to the documentation of this file.
1#include "WoWItem.h"
2
3#include <algorithm>
4#include <format>
5#include <fstream>
6#include <set>
7#include <sstream>
8
9#include "Attachment.h"
10#include "database.h" // items
11#include "DB2Table.h"
12#include "Game.h"
13#include "RaceInfos.h"
14#include "wow_enums.h"
15#include "WoWModel.h"
16
17#include "Logger.h"
18#include "string_utils.h"
19
20std::map<CharSlots, int> WoWItem::SLOT_LAYERS_ =
21{
22 {CS_SHIRT, 10}, {CS_HEAD, 11}, {CS_SHOULDER, 13},
23 {CS_PANTS, 10}, {CS_BOOTS, 11}, {CS_CHEST, 13},
24 {CS_TABARD, 17}, {CS_BELT, 18}, {CS_BRACERS, 19},
25 {CS_GLOVES, 20}, {CS_HAND_RIGHT, 21}, {CS_HAND_LEFT, 22},
26 {CS_CAPE, 23}, {CS_QUIVER, 24}
27};
28
29WoWItem::WoWItem(CharSlots slot) : slot_(slot)
30{
31 setName("---- None ----");
32}
33
34void WoWItem::setId(int id)
35{
36 if (id != id_)
37 {
38 id_ = id;
39
40 if (id_ == 0)
41 {
42 unload();
43 // reset name and quality
44 setName("---- None ----");
45 quality_ = 0;
46 type_ = 0;
47
48 if (slot_ == CS_HAND_RIGHT)
50
51 if (slot_ == CS_HAND_LEFT)
53
54 return;
55 }
56
57 const DB2Table* imaTbl = WOWDB.getTable("ItemModifiedAppearance");
58 if (imaTbl)
59 {
60 nbLevels_ = 0;
61 level_ = 0;
62 levelDisplayMap_.clear();
63 for (const auto& row : *imaTbl)
64 {
65 if (static_cast<int>(row.getUInt("ItemID")) != id)
66 continue;
67
68 const auto curid = static_cast<int>(row.getUInt("ItemAppearanceID"));
69 modifierIdDisplayMap_[static_cast<int>(row.getUInt("ItemAppearanceModifierID"))] = curid;
70 // if display id is null (case when item's look doesn't change with level)
71 if (curid == 0)
72 continue;
73
74 //check if display id already in the map (do not duplicate when look is the same)
75 auto found = false;
76 for (const auto& it : levelDisplayMap_)
77 {
78 if (it.second == curid)
79 {
80 found = true;
81 break;
82 }
83 }
84
85 if (!found)
86 {
88 nbLevels_++;
89 }
90 }
91 }
92
93 if (levelDisplayMap_.count(level_))
94 {
95 const DB2Table* iaTbl = WOWDB.getTable("ItemAppearance");
96 if (iaTbl)
97 {
98 DB2Row iaRow = iaTbl->getRow(levelDisplayMap_[level_]);
99 if (iaRow)
100 displayId_ = static_cast<int>(iaRow.getUInt("ItemDisplayInfoID"));
101 }
102
103 const auto& itemRcd = items.getById(id);
104 setName(itemRcd.name);
105 quality_ = itemRcd.quality;
106 type_ = itemRcd.type;
107 load();
108 }
109 }
110 }
111
113{
114 if (displayId_ != id)
115 {
116 id_ = -1;
117 displayId_ = id; // to update from database;
118 setName("NPC Item");
119 load();
120 }
121}
122
123void WoWItem::setLevel(int level)
124{
125 if ((nbLevels_ > 1) && (level_ != level))
126 {
127 level_ = level;
128
129 const DB2Table* iaTbl = WOWDB.getTable("ItemAppearance");
130 if (iaTbl)
131 {
132 DB2Row iaRow = iaTbl->getRow(levelDisplayMap_[level_]);
133 if (iaRow)
134 displayId_ = static_cast<int>(iaRow.getUInt("ItemDisplayInfoID"));
135 }
136
137 const auto& itemRcd = items.getById(id_);
138 setName(itemRcd.name);
139 quality_ = itemRcd.quality;
140 type_ = itemRcd.type;
141 load();
142 }
143}
144
146{
147 const auto it = modifierIdDisplayMap_.find(id);
148 if (it != modifierIdDisplayMap_.end())
149 {
150 const DB2Table* iaTbl = WOWDB.getTable("ItemAppearance");
151 if (iaTbl)
152 {
153 DB2Row iaRow = iaTbl->getRow(it->second);
154 if (iaRow)
155 displayId_ = static_cast<int>(iaRow.getUInt("ItemDisplayInfoID"));
156 }
157
158 const auto& itemRcd = items.getById(id_);
159 setName(itemRcd.name);
160 quality_ = itemRcd.quality;
161 type_ = itemRcd.type;
162 load();
163 }
164}
165
167{
168 charModel_ = dynamic_cast<WoWModel*>(parent);
169}
170
172{
173 // delete models and clear map
174 for (const auto& itemModel : itemModels_)
175 delete itemModel.second;
176
177 itemModels_.clear();
178
179 // release textures and clear map
180 for (const auto& itemTexture : itemTextures_)
181 TEXTUREMANAGER.delbyname(itemTexture.second->fullname());
182
183 itemTextures_.clear();
184
185 // clear map
186 itemGeosets_.clear();
187
188 // remove any existing attachement
191
192 // unload any merged model
193 if (mergedModel_ != nullptr)
194 {
195 // TODO : unmerge trigs refreshMerging that trigs refresh... so mergedModel_ must be null...
196 // need to find a better way to solve this
197 const auto m = mergedModel_;
198 mergedModel_ = nullptr;
200 delete m;
201 }
202}
203
205{
206 unload();
207
208 if (!charModel_) // no parent => give up
209 return;
210
211 if (id_ == 0 || displayId_ == 0) // no equipment, just return
212 return;
213
214 const auto charInfos = charModel_->infos;
215
216 // query geosets infos from ItemDisplayInfo
217 const DB2Table* idiTbl = WOWDB.getTable("ItemDisplayInfo");
218 if (!idiTbl)
219 return;
220
221 DB2Row idiRow = idiTbl->getRow(displayId_);
222 if (!idiRow)
223 {
224 LOG_ERROR << "Impossible to query information for item" << name() << "(id " << id_ << "- display id" <<
225 displayId_ << ")";
226 return;
227 }
228
229 const int geosetGroup[6] = {
230 idiRow.getInt("GeoSetGroup1"), idiRow.getInt("GeoSetGroup2"),
231 idiRow.getInt("GeoSetGroup3"), idiRow.getInt("GeoSetGroup4"),
232 idiRow.getInt("GeoSetGroup5"), idiRow.getInt("GeoSetGroup6")
233 };
234
235 const int attachmentGeosetGroup[6] =
236 {
237 idiRow.getInt("AttachmentGeoSetGroup1"), idiRow.getInt("AttachmentGeoSetGroup2"),
238 idiRow.getInt("AttachmentGeoSetGroup3"), idiRow.getInt("AttachmentGeoSetGroup4"),
239 idiRow.getInt("AttachmentGeoSetGroup5"), idiRow.getInt("AttachmentGeoSetGroup6")
240 };
241
242 displayFlags_ = idiRow.getInt("Flags");
243
244 // query models
245 const int models[2] = {getCustomModelId(0), getCustomModelId(1)};
246
247 // query textures
248 const int textures[2] = {getCustomTextureId(0), getCustomTextureId(1)};
249
250 // query textures from ItemDisplayInfoMaterialRes (if relevant)
251 const DB2Table* matResTbl = WOWDB.getTable("ItemDisplayInfoMaterialRes");
252 const DB2Table* texFileTbl = WOWDB.getTable("TextureFileData");
253 const DB2Table* compTexTbl = WOWDB.getTable("ComponentTextureFileData");
254
255 if (matResTbl && texFileTbl && compTexTbl)
256 {
257 // Collect MaterialResourcesIDs for this display
258 std::vector<uint32_t> materialResIds;
259 for (const auto& mrRow : *matResTbl)
260 {
261 if (static_cast<int>(mrRow.getUInt("ItemDisplayInfoID")) == displayId_)
262 materialResIds.push_back(mrRow.getUInt("MaterialResourcesID"));
263 }
264
265 if (!materialResIds.empty())
266 {
267 const bool isDH = charModel_ && charModel_->cd.isDemonHunter();
268
269 // Build candidate FileDataIDs via TextureFileData JOIN ComponentTextureFileData
270 struct TexCandidate { int fileDataID; int genderIndex; int classID; };
271 std::vector<TexCandidate> candidates;
272
273 for (const auto& tfdRow : *texFileTbl)
274 {
275 const uint32_t tfdMatResID = tfdRow.getUInt("MaterialResourcesID");
276 bool matchesMat = false;
277 for (uint32_t mrid : materialResIds)
278 {
279 if (tfdMatResID == mrid) { matchesMat = true; break; }
280 }
281 if (!matchesMat)
282 continue;
283
284 const int fileDataID = static_cast<int>(tfdRow.getUInt("FileDataID"));
285 DB2Row ctRow = compTexTbl->getRow(fileDataID);
286 if (!ctRow)
287 continue;
288
289 const int genderIdx = ctRow.getInt("GenderIndex");
290 const int classID = ctRow.getInt("ClassID");
291
292 // Filter: gender must be GENDER_ANY or sexID
293 if (genderIdx != static_cast<int>(GENDER_ANY) && genderIdx != charInfos.sexID)
294 continue;
295
296 // Filter: class must match
297 if (isDH)
298 {
299 if (classID != static_cast<int>(CLASS_DEMONHUNTER) && classID != static_cast<int>(CLASS_ANY))
300 continue;
301 }
302 else
303 {
304 if (classID != static_cast<int>(CLASS_ANY))
305 continue;
306 }
307
308 candidates.push_back({fileDataID, genderIdx, classID});
309 }
310
311 // Sort: GenderIndex ASC, ClassID DESC
312 std::sort(candidates.begin(), candidates.end(), [](const TexCandidate& a, const TexCandidate& b) {
313 if (a.genderIndex != b.genderIndex) return a.genderIndex < b.genderIndex;
314 return a.classID > b.classID;
315 });
316
317 for (const auto& c : candidates)
318 {
319 const auto tex = GAMEDIRECTORY.getFile(c.fileDataID);
320 if (tex)
321 {
322 auto texRegion = getRegionForTexture(tex);
323 // Only add one texture per region (first one in sort order):
324 if (itemTextures_.count(texRegion) < 1)
325 {
326 TEXTUREMANAGER.add(tex);
327 itemTextures_[texRegion] = tex;
328 }
329 }
330 }
331 }
332 }
333
334 switch (slot_)
335 {
336 case CS_HEAD:
337 {
338 // attachments
339 updateItemModel(ATT_HELMET, models[0], textures[0]);
340
341 // geosets
342 // Head: {geosetGroup[0] = 2700**, geosetGroup[1] = 2101 }
343 itemGeosets_[CG_GEOSET2700] = 1 + geosetGroup[0];
344 itemGeosets_[CG_GEOSET2100] = 1 + geosetGroup[1];
345
346 // 'collections' models:
347 if (models[1] != 0)
348 {
349 mergeModel(CS_HEAD, models[1], textures[1]);
350 mergedModel_->setGeosetGroupDisplay(CG_GEOSET2700, 1 + attachmentGeosetGroup[0]);
351 mergedModel_->setGeosetGroupDisplay(CG_GEOSET2100, 1 + attachmentGeosetGroup[1]);
352 }
353
354 break;
355 }
356 case CS_SHOULDER:
357 {
358 // geosets
359 // Shoulder: {geosetGroup[0] = 2601}
360 itemGeosets_[CG_GEOSET2600] = 1 + geosetGroup[0];
361
362 // find position index value from ComponentModelFileData table
363 const DB2Table* cmfTbl = WOWDB.getTable("ComponentModelFileData");
364
365 auto leftIndex = 0;
366 auto rightIndex = 1;
367 if (cmfTbl)
368 {
369 DB2Row cmfRow = cmfTbl->getRow(models[0]);
370 if (!cmfRow)
371 cmfRow = cmfTbl->getRow(models[1]);
372
373 if (cmfRow)
374 {
375 const auto modelid = static_cast<int>(cmfRow.recordID());
376 const auto position = cmfRow.getInt("PositionIndex");
377
378 // If the modelid matches models[0], use position to determine left/right
379 // Otherwise, swap the indices
380 if ((modelid == models[0] && position != 0) || (modelid != models[0] && position == 0))
381 {
382 leftIndex = 1;
383 rightIndex = 0;
384 }
385 }
386 else
387 {
388 LOG_ERROR << "Impossible to query information for item" << name() << "(id " << id_ << "- display id" <<
389 displayId_ << ")";
390 }
391 }
392
393 LOG_INFO << "leftIndex" << leftIndex << "rightIndex" << rightIndex;
394
395 // left shoulder
396 updateItemModel(ATT_LEFT_SHOULDER, models[leftIndex], textures[leftIndex]);
397
398 // right shoulder
399 updateItemModel(ATT_RIGHT_SHOULDER, models[rightIndex], textures[rightIndex]);
400
401 break;
402 }
403 case CS_BOOTS:
404 {
405 // geosets
406 // Boots: {geosetGroup[0] = 501, geosetGroup[1] = 2000*}
407 itemGeosets_[CG_BOOTS] = 1 + geosetGroup[0];
408 // geoset group 20 (CG_FEET) is handled a bit differently, according to wowdev.wiki:
409 if (geosetGroup[1] == 0)
411 else if (geosetGroup[1] > 0)
412 itemGeosets_[CG_FEET] = geosetGroup[1];
413 // else ? should we do anything if geosetGroup[1] < 0?
414
415 // 'collections' models:
416 if (models[0] != 0)
417 {
418 mergeModel(CS_BOOTS, models[0], textures[0]);
419 mergedModel_->setGeosetGroupDisplay(CG_BOOTS, 1 + attachmentGeosetGroup[0]);
420 mergedModel_->setGeosetGroupDisplay(CG_FEET, 1 + attachmentGeosetGroup[1]);
421 }
422
423 break;
424 }
425 case CS_BELT:
426 {
427 // geosets
428 // Waist: {geosetGroup[0] = 1801}
429 itemGeosets_[CG_BELT] = 1 + geosetGroup[0];
430
431 // buckle model
432 updateItemModel(ATT_BELT_BUCKLE, models[0], textures[0]);
433
434 // 'collections' models:
435 if (models[1] != 0)
436 {
437 mergeModel(CS_BELT, models[1], textures[1]);
438 mergedModel_->setGeosetGroupDisplay(CG_BELT, 1 + attachmentGeosetGroup[0]);
439 }
440
441 break;
442 }
443 case CS_PANTS:
444 {
445 // geosets
446 // Pants: {geosetGroup[0] = 1101, geosetGroup[1] = 901, geosetGroup[2] = 1301}
447 itemGeosets_[CG_PANTS] = 1 + geosetGroup[0];
448 itemGeosets_[CG_KNEEPADS] = 1 + geosetGroup[1];
449 itemGeosets_[CG_TROUSERS] = 1 + geosetGroup[2];
450
451 // 'collections' models:
452 if (models[0] != 0)
453 {
454 mergeModel(CS_PANTS, models[0], textures[0]);
455 mergedModel_->setGeosetGroupDisplay(CG_PANTS, 1 + attachmentGeosetGroup[0]);
456 mergedModel_->setGeosetGroupDisplay(CG_KNEEPADS, 1 + attachmentGeosetGroup[1]);
457 mergedModel_->setGeosetGroupDisplay(CG_TROUSERS, 1 + attachmentGeosetGroup[2]);
458 }
459
460 break;
461 }
462 case CS_SHIRT:
463 case CS_CHEST:
464 {
465 // geosets
466 // Chest: {geosetGroup[0] = 801, geosetGroup[1] = 1001, geosetGroup[2] = 1301, geosetGroup[3] = 2201, geosetGroup[4] = 2801}
467 itemGeosets_[CG_SLEEVES] = 1 + geosetGroup[0];
468 itemGeosets_[CG_CHEST] = 1 + geosetGroup[1];
469 itemGeosets_[CG_TROUSERS] = 1 + geosetGroup[2];
470 itemGeosets_[CG_TORSO] = 1 + geosetGroup[3];
471 itemGeosets_[CG_GEOSET2800] = 1 + geosetGroup[4];
472
473 // 'collections' models:
474 if (models[0] != 0)
475 {
476 mergeModel(CS_CHEST, models[0], textures[0]);
477 mergedModel_->setGeosetGroupDisplay(CG_SLEEVES, 1 + attachmentGeosetGroup[0]);
478 mergedModel_->setGeosetGroupDisplay(CG_CHEST, 1 + attachmentGeosetGroup[1]);
479 mergedModel_->setGeosetGroupDisplay(CG_TROUSERS, 1 + attachmentGeosetGroup[2]);
480 mergedModel_->setGeosetGroupDisplay(CG_TORSO, 1 + attachmentGeosetGroup[3]);
481 mergedModel_->setGeosetGroupDisplay(CG_GEOSET2800, 1 + attachmentGeosetGroup[4]);
482 }
483
484 break;
485 }
486 case CS_BRACERS:
487 {
488 // nothing specific for bracers
489 break;
490 }
491 case CS_GLOVES:
492 {
493 // geosets
494 // Gloves: {geosetGroup[0] = 401, geosetGroup[1] = 2301}
495 itemGeosets_[CG_GLOVES] = 1 + geosetGroup[0];
496 itemGeosets_[CG_HAND_ATTACHMENT] = 1 + geosetGroup[1];
497
498 // 'collections' models:
499 if (models[0] != 0)
500 {
501 mergeModel(CS_GLOVES, models[0], textures[0]);
502 mergedModel_->setGeosetGroupDisplay(CG_GLOVES, 1 + attachmentGeosetGroup[0]);
503 mergedModel_->setGeosetGroupDisplay(CG_HAND_ATTACHMENT, 1 + attachmentGeosetGroup[1]);
504 }
505
506 break;
507 }
508 case CS_HAND_RIGHT:
509 case CS_HAND_LEFT:
510 {
512 break;
513 }
514 case CS_CAPE:
515 {
516 auto* tex = GAMEDIRECTORY.getFile(textures[0]);
517 if (tex)
518 {
519 TEXTUREMANAGER.add(tex);
521 }
522
523 // geosets
524 // Cape: {geosetGroup[0] = 1501}
525 itemGeosets_[CG_CLOAK] = 1 + geosetGroup[0];
526
527 // 'collections' models:
528 if (models[0] != 0)
529 {
530 mergeModel(CS_CAPE, models[0], textures[0]);
531 mergedModel_->setGeosetGroupDisplay(CG_CLOAK, 1 + attachmentGeosetGroup[0]);
532 }
533
534 break;
535 }
536 case CS_TABARD:
537 {
539 {
540 charModel_->td.showCustom = true;
542
544 if (texture)
545 {
546 TEXTUREMANAGER.add(texture);
547 itemTextures_[CR_TABARD_1] = texture;
548 }
549
551 if (texture)
552 {
553 TEXTUREMANAGER.add(texture);
554 itemTextures_[CR_TABARD_2] = texture;
555 }
556
558 if (texture)
559 {
560 TEXTUREMANAGER.add(texture);
561 itemTextures_[CR_TABARD_3] = texture;
562 }
563
565 if (texture)
566 {
567 TEXTUREMANAGER.add(texture);
568 itemTextures_[CR_TABARD_4] = texture;
569 }
570
572 if (texture)
573 {
574 TEXTUREMANAGER.add(texture);
575 itemTextures_[CR_TABARD_5] = texture;
576 }
577
579 if (texture)
580 {
581 TEXTUREMANAGER.add(texture);
582 itemTextures_[CR_TABARD_6] = texture;
583 }
584 }
585 else
586 {
587 charModel_->td.showCustom = false;
588
589 // geosets
590 // Tabard: {geosetGroup[0] = 1201}
591 itemGeosets_[CG_TABARD] = 1 + geosetGroup[0];
592 }
593
594 break;
595 }
596 case CS_QUIVER:
597 break;
598 default:
599 break;
600 }
601}
602
604{
605 if (id_ == 0) // no item equipped, give up
606 return;
607
608 // merge model if any
609 if (mergedModel_ != nullptr)
611
612 // update geoset values
613 for (const auto it : itemGeosets_)
614 {
615 if ((slot_ != CS_BOOTS) && // treat boots geoset in a special case - cf CS_BOOTS
616 (slot_ != CS_PANTS)) // treat trousers geoset in a special case - cf CS_PANTS
617 {
618 charModel_->cd.geosets[it.first] = it.second;
619 /*
620 if (mergedModel_ != 0)
621 mergedModel_->setGeosetGroupDisplay(it.first, 1);
622 */
623 }
624 }
625
626 // attach items if any
628 {
629 if ((slot_ != CS_HAND_RIGHT) && // treat right hand attachment in a special case - cf CS_HAND_RIGHT
630 (slot_ != CS_HAND_LEFT)) // treat left hand attachment in a special case - cf CS_HAND_LEFT
631 {
633 for (const auto it : itemModels_)
634 charModel_->attachment->addChild(it.second, it.first, slot_);
635 }
636 }
637
638 // add textures if any
639 if ((slot_ != CS_BOOTS) && // treat boots texturing in a special case - cf CS_BOOTS
640 (slot_ != CS_GLOVES) && // treat gloves texturing in a special case - cf CS_GLOVES
641 (slot_ != CS_TABARD) && // treat tabard texturing in a special case - cf CS_TABARD
642 (slot_ != CS_CAPE)) // treat cape texturing in a special case - cf CS_CAPE
643 {
644 for (const auto it : itemTextures_)
645 charModel_->tex.addLayer(it.second, it.first, SLOT_LAYERS_[slot_]);
646 }
647
648 switch (slot_)
649 {
650 case CS_HEAD:
651 {
652 // nothing specific for head items
653 break;
654 }
655 case CS_SHOULDER:
656 {
657 // nothing specific for shoulder items
658 break;
659 }
660 case CS_HAND_RIGHT:
661 {
663 {
665
666 const auto it = itemModels_.find(ATT_RIGHT_PALM);
667 if (it != itemModels_.end())
668 {
669 int attachement = ATT_RIGHT_PALM;
670 const auto& item = items.getById(id_);
671 if (charModel_->bSheathe && item.sheath != SHEATHETYPE_NONE)
672 {
673 // make the weapon cross
674 if (item.sheath == ATT_LEFT_BACK_SHEATH)
675 attachement = ATT_RIGHT_BACK_SHEATH;
676 if (item.sheath == ATT_LEFT_BACK)
677 attachement = ATT_RIGHT_BACK;
678 if (item.sheath == ATT_LEFT_HIP_SHEATH)
679 attachement = ATT_RIGHT_HIP_SHEATH;
680 }
681
682 if (charModel_->bSheathe)
684 else
686
687 charModel_->attachment->addChild(it->second, attachement, slot_);
688 }
689 }
690 break;
691 }
692 case CS_HAND_LEFT:
693 {
695 {
697
698 const auto it = itemModels_.find(ATT_LEFT_PALM);
699 if (it != itemModels_.end())
700 {
701 const auto& item = items.getById(id_);
702 int attachement = ATT_LEFT_PALM;
703
704 if (item.type == IT_SHIELD)
705 attachement = ATT_LEFT_WRIST;
706
707 if (charModel_->bSheathe && item.sheath != SHEATHETYPE_NONE)
708 attachement = static_cast<POSITION_SLOTS>(item.sheath);
709
710 if (charModel_->bSheathe || item.type == IT_SHIELD)
712 else
714
715 // if (displayFlags_ & 0x100) then item should be mirrored when in left hand:
716 it->second->mirrored_ = (displayFlags_ & 0x100);
717 charModel_->attachment->addChild(it->second, attachement, slot_);
718 }
719 }
720 break;
721 }
722 case CS_BELT:
723 {
724 // nothing specific for belt items
725 break;
726 }
727 case CS_BOOTS:
728 {
729 for (const auto it : itemGeosets_)
730 {
731 if (it.first != CG_BOOTS && !charModel_->isWearingARobe())
732 {
733 charModel_->cd.geosets[it.first] = it.second;
734 /*
735 if (mergedModel_ != 0)
736 mergedModel_->setGeosetGroupDisplay(it.first, 1);
737 */
738 }
739 else
740 {
741 // don't render boots behind robe
743 {
744 charModel_->cd.geosets[it.first] = it.second;
745 /*
746 if (mergedModel_ != 0)
747 mergedModel_->setGeosetGroupDisplay(CG_BOOTS, 1);
748 */
749 }
750 }
751 }
752
753 auto texIt = itemTextures_.find(CR_LEG_LOWER);
754 if (texIt != itemTextures_.end())
756
757 if (!charModel_->cd.showFeet)
758 {
759 texIt = itemTextures_.find(CR_FOOT);
760 if (texIt != itemTextures_.end())
761 charModel_->tex.addLayer(texIt->second, CR_FOOT, SLOT_LAYERS_[slot_]);
762 }
763 break;
764 }
765 case CS_PANTS:
766 {
767 for (const auto it : itemGeosets_)
768 {
769 if (it.first != CG_TROUSERS)
770 {
771 charModel_->cd.geosets[it.first] = it.second;
772 /*
773 if (mergedModel_ != 0)
774 mergedModel_->setGeosetGroupDisplay(it.first, 1);
775 */
776 }
777 }
778
779 const auto geoIt = itemGeosets_.find(CG_TROUSERS);
780
781 if (geoIt != itemGeosets_.end())
782 {
783 // apply trousers geosets only if character is not already wearing a robe
785 {
786 charModel_->cd.geosets[CG_TROUSERS] = geoIt->second;
787 /*
788 if (mergedModel_)
789 mergedModel_->setGeosetGroupDisplay(CG_TROUSERS, 1);
790 */
791 }
792 }
793
794 break;
795 }
796 case CS_SHIRT:
797 case CS_CHEST: // nothing specific for shirt & chest items
798 break;
799 case CS_BRACERS: // nothing specific for bracers items
800 break;
801 case CS_GLOVES:
802 {
803 auto texIt = itemTextures_.find(CR_ARM_LOWER);
804
805 auto layer = SLOT_LAYERS_[slot_];
806
807 // if we are wearing a robe, render gloves first in texture compositing
808 // only if GeoSetGroup1 is 0 (from item displayInfo db) which corresponds to stored geoset equals to 1
810 layer = SLOT_LAYERS_[CS_CHEST] - 1;
811
812 if (texIt != itemTextures_.end())
813 charModel_->tex.addLayer(texIt->second, CR_ARM_LOWER, layer);
814
815 texIt = itemTextures_.find(CR_HAND);
816 if (texIt != itemTextures_.end())
817 charModel_->tex.addLayer(texIt->second, CR_HAND, layer);
818 break;
819 }
820 case CS_CAPE:
821 {
822 const auto it = itemTextures_.find(CR_CAPE);
823 if (it != itemTextures_.end())
825 break;
826 }
827 case CS_TABARD:
828 {
830 {
831 static const std::pair<CharRegions, CharRegions> tabardLayers[] = {
838 };
839 for (const auto& layer : tabardLayers)
840 {
841 auto it = itemTextures_.find(layer.first);
842 if (it != itemTextures_.end())
843 charModel_->tex.addLayer(it->second, layer.second, SLOT_LAYERS_[slot_]);
844 }
845 }
846 else
847 {
848 auto it = itemTextures_.find(CR_TORSO_UPPER);
849 if (it != itemTextures_.end())
851
852 it = itemTextures_.find(CR_TORSO_LOWER);
853 if (it != itemTextures_.end())
855 }
856 break;
857 }
858 default:
859 break;
860 }
861}
862
864{
865 return (id_ == 5976 || // Guild Tabard
866 id_ == 69209 || // Illustrious Guild Tabard
867 id_ == 69210); // Renowned Guild Tabard
868}
869
870void WoWItem::save(pugi::xml_node& parentNode) const
871{
872 pugi::xml_node node = parentNode.append_child("item");
873
874 node.append_child("slot").append_attribute("value") = static_cast<int>(slot_);
875 node.append_child("id").append_attribute("value") = id_;
876 node.append_child("displayId").append_attribute("value") = displayId_;
877 node.append_child("level").append_attribute("value") = level_;
878
880 charModel_->td.save(node);
881}
882
883void WoWItem::load(const std::string& f)
884{
885 pugi::xml_document doc;
886 pugi::xml_parse_result result = doc.load_file(f.c_str());
887 if (!result)
888 {
889 LOG_ERROR << "Fail to open" << f.c_str();
890 return;
891 }
892
893 // Find all item nodes and look for the matching slot
894 pugi::xml_node root = doc.document_element();
895 for (pugi::xml_node itemNode = root.child("item"); itemNode; itemNode = itemNode.next_sibling("item"))
896 {
897 pugi::xml_node slotNode = itemNode.child("slot");
898 if (!slotNode)
899 continue;
900
901 const auto slot = slotNode.attribute("value").as_uint();
902 if (slot != static_cast<unsigned int>(slot_))
903 continue;
904
905 pugi::xml_node idNode = itemNode.child("id");
906 if (idNode)
907 {
908 const auto id = idNode.attribute("value").as_int();
909 if (id != -1)
910 setId(id);
911 }
912
913 pugi::xml_node displayIdNode = itemNode.child("displayId");
914 if (displayIdNode)
915 {
916 const auto id = displayIdNode.attribute("value").as_int();
917 if (id_ == -1)
918 setDisplayId(id);
919 }
920
921 pugi::xml_node levelNode = itemNode.child("level");
922 if (levelNode)
923 {
924 const auto level = levelNode.attribute("value").as_int();
925 setLevel(level);
926 }
927
929 {
930 pugi::xml_node tabardNode = itemNode.child("TabardDetails");
931 if (tabardNode)
932 {
933 charModel_->td.load(tabardNode);
934 load(); // refresh tabard textures
935 }
936 }
937
938 break; // found matching slot
939 }
940}
941
942void WoWItem::updateItemModel(POSITION_SLOTS pos, int modelId, int textureId)
943{
944 if (modelId == 0)
945 return;
946
947 auto* m = new WoWModel(GAMEDIRECTORY.getFile(modelId), true);
948
949 if (m->ok)
950 {
951 for (uint i = 0; i < m->geosets.size(); i++)
952 m->showGeoset(i, true);
953
954 itemModels_[pos] = m;
955 auto* texture = GAMEDIRECTORY.getFile(textureId);
956 if (texture)
957 m->updateTextureList(texture, TEXTURE_OBJECT_SKIN);
958 else
959 LOG_ERROR << "Error during item update" << id_ << "(display id" << displayId_ << "). Texture" << textureId
960 << "can't be loaded";
961 }
962 else
963 {
964 LOG_ERROR << "Error during item update" << id_ << "(display id" << displayId_ << "). Model" << modelId <<
965 "can't be loaded";
966 }
967}
968
969void WoWItem::mergeModel(CharSlots slot, int modelId, int textureId)
970{
971 if (modelId == 0)
972 return;
973
974 mergedModel_ = new WoWModel(GAMEDIRECTORY.getFile(modelId), true);
975
976 if (mergedModel_->ok)
977 {
978 auto* texture = GAMEDIRECTORY.getFile(textureId);
979 if (texture)
980 {
983 }
984 else
985 LOG_ERROR << "Error during item update" << id_ << "(display id" << displayId_ << "). Texture" << textureId
986 << "can't be loaded";
987
988 for (uint i = 0; i < mergedModel_->geosets.size(); i++)
990 }
991 else
992 {
993 LOG_ERROR << "Error during item update" << id_ << "(display id" << displayId_ << "). Model" << modelId <<
994 "can't be loaded";
995 }
996}
997
999{
1000 auto result = CR_UNK8;
1001
1002 if (file)
1003 {
1004 std::string fullname = file->fullname();
1005 std::transform(fullname.begin(), fullname.end(), fullname.begin(),
1006 [](unsigned char c) { return std::tolower(c); });
1007
1008 if (fullname.find("armlowertexture") != std::string::npos)
1009 {
1010 result = CR_ARM_LOWER;
1011 }
1012 else if (fullname.find("armuppertexture") != std::string::npos)
1013 {
1014 result = CR_ARM_UPPER;
1015 }
1016 else if (fullname.find("foottexture") != std::string::npos)
1017 {
1018 result = CR_FOOT;
1019 }
1020 else if (fullname.find("handtexture") != std::string::npos)
1021 {
1022 result = CR_HAND;
1023 }
1024 else if (fullname.find("leglowertexture") != std::string::npos)
1025 {
1026 result = CR_LEG_LOWER;
1027 }
1028 else if (fullname.find("leguppertexture") != std::string::npos)
1029 {
1030 result = CR_LEG_UPPER;
1031 }
1032 else if (fullname.find("torsolowertexture") != std::string::npos)
1033 {
1034 result = CR_TORSO_LOWER;
1035 }
1036 else if (fullname.find("torsouppertexture") != std::string::npos)
1037 {
1038 result = CR_TORSO_UPPER;
1039 }
1040 else if (fullname.find("cape") != std::string::npos)
1041 {
1042 result = CR_CAPE;
1043 }
1044 else
1045 {
1046 LOG_ERROR << "Unable to determine region for texture" << file->fullname().c_str() << " - item" << id_ << "displayid" <<
1047 displayId_;
1048 }
1049 }
1050
1051 return result;
1052}
1053
1054// Helper: find the best ComponentModelFileData match from a set of candidate IDs,
1055// filtering by raceID, genderIndex, classID, and sorting by GenderIndex ASC, ClassID DESC, PositionIndex ASC/DESC
1056static int findBestComponentModel(const DB2Table* cmfTbl, const std::set<int>& candidateIds,
1057 int raceID, int sexID, int altGender, bool isDH, bool positionDesc)
1058{
1059 struct Candidate { int id; int genderIndex; int classID; int positionIndex; };
1060 std::vector<Candidate> matches;
1061
1062 for (const auto& row : *cmfTbl)
1063 {
1064 const int rowID = static_cast<int>(row.recordID());
1065 if (candidateIds.find(rowID) == candidateIds.end())
1066 continue;
1067
1068 const int rowRace = row.getInt("RaceID");
1069 if (rowRace != raceID)
1070 continue;
1071
1072 const int rowGender = row.getInt("GenderIndex");
1073 if (rowGender != sexID && rowGender != altGender)
1074 continue;
1075
1076 const int rowClass = row.getInt("ClassID");
1077 if (isDH)
1078 {
1079 if (rowClass != static_cast<int>(CLASS_DEMONHUNTER) && rowClass != static_cast<int>(CLASS_ANY))
1080 continue;
1081 }
1082 else
1083 {
1084 if (rowClass != static_cast<int>(CLASS_ANY))
1085 continue;
1086 }
1087
1088 matches.push_back({rowID, rowGender, rowClass, row.getInt("PositionIndex")});
1089 }
1090
1091 if (matches.empty())
1092 return 0;
1093
1094 // Sort: GenderIndex ASC, ClassID DESC, PositionIndex ASC or DESC
1095 std::sort(matches.begin(), matches.end(), [positionDesc](const Candidate& a, const Candidate& b) {
1096 if (a.genderIndex != b.genderIndex) return a.genderIndex < b.genderIndex;
1097 if (a.classID != b.classID) return a.classID > b.classID;
1098 return positionDesc ? (a.positionIndex > b.positionIndex) : (a.positionIndex < b.positionIndex);
1099 });
1100
1101 return matches[0].id;
1102}
1103
1104// Helper: find the best ComponentTextureFileData match from a set of candidate IDs
1105static int findBestComponentTexture(const DB2Table* ctfTbl, const std::set<int>& candidateIds,
1106 int raceID, int sexID, int altGender, bool isDH)
1107{
1108 struct Candidate { int id; int genderIndex; int classID; };
1109 std::vector<Candidate> matches;
1110
1111 for (const auto& row : *ctfTbl)
1112 {
1113 const int rowID = static_cast<int>(row.recordID());
1114 if (candidateIds.find(rowID) == candidateIds.end())
1115 continue;
1116
1117 const int rowRace = row.getInt("RaceID");
1118 if (rowRace != raceID)
1119 continue;
1120
1121 const int rowGender = row.getInt("GenderIndex");
1122 if (rowGender != sexID && rowGender != altGender)
1123 continue;
1124
1125 const int rowClass = row.getInt("ClassID");
1126 if (isDH)
1127 {
1128 if (rowClass != static_cast<int>(CLASS_DEMONHUNTER) && rowClass != static_cast<int>(CLASS_ANY))
1129 continue;
1130 }
1131 else
1132 {
1133 if (rowClass != static_cast<int>(CLASS_ANY))
1134 continue;
1135 }
1136
1137 matches.push_back({rowID, rowGender, rowClass});
1138 }
1139
1140 if (matches.empty())
1141 return 0;
1142
1143 // Sort: GenderIndex ASC, ClassID DESC
1144 std::sort(matches.begin(), matches.end(), [](const Candidate& a, const Candidate& b) {
1145 if (a.genderIndex != b.genderIndex) return a.genderIndex < b.genderIndex;
1146 return a.classID > b.classID;
1147 });
1148
1149 return matches[0].id;
1150}
1151
1152int WoWItem::getCustomModelId(size_t index) const
1153{
1154 if (!charModel_)
1155 return 0;
1156
1157 // Step 1: Get ModelResourcesID from ItemDisplayInfo
1158 const DB2Table* idiTbl = WOWDB.getTable("ItemDisplayInfo");
1159 if (!idiTbl)
1160 return 0;
1161
1162 DB2Row idiRow = idiTbl->getRow(displayId_);
1163 if (!idiRow)
1164 return 0;
1165
1166 const uint32_t modelResID = (index == 0) ? idiRow.getUInt("ModelResourcesID1") : idiRow.getUInt("ModelResourcesID2");
1167 if (modelResID == 0)
1168 return 0;
1169
1170 // Step 2: Find FileDataIDs from ModelFileData where ModelResourcesID matches
1171 const DB2Table* mfdTbl = WOWDB.getTable("ModelFileData");
1172 if (!mfdTbl)
1173 return 0;
1174
1175 std::set<int> candidateIds;
1176 for (const auto& row : *mfdTbl)
1177 {
1178 if (row.getUInt("ModelResourcesID") == modelResID)
1179 candidateIds.insert(static_cast<int>(row.getUInt("FileDataID")));
1180 }
1181
1182 if (candidateIds.empty())
1183 return 0;
1184
1185 // if there is only one result, return model id:
1186 if (candidateIds.size() == 1)
1187 return *candidateIds.begin();
1188
1189 // Step 3: Filter through ComponentModelFileData by race/sex/class
1190 const DB2Table* cmfTbl = WOWDB.getTable("ComponentModelFileData");
1191 if (!cmfTbl)
1192 return 0;
1193
1194 const auto charInfos = charModel_->infos;
1195 const bool isDH = charModel_->cd.isDemonHunter();
1196
1197 // It looks like shoulders are always in pairs, with PositionIndex values 0 and 1.
1198 // Depending on index (model 1 or 2) we sort the PositionIndex differently so one will
1199 // return left and one right shoulder. Noting this in case in the future it turns out
1200 // this assumption isn't always right - Wain
1201 const bool positionDesc = (index != 0);
1202
1203 // Order all queries by GenderIndex to ensure definite genders have priority over generic ones,
1204 // and ClassID descending to ensure Demon Hunter textures have priority over regular ones, for DHs only:
1205 int result = findBestComponentModel(cmfTbl, candidateIds, charInfos.raceID, charInfos.sexID,
1206 static_cast<int>(GENDER_ANY), isDH, positionDesc);
1207 if (result != 0)
1208 return result;
1209
1210 // Failed to find model for that specific race and sex, so check fallback race:
1211 if (charInfos.modelFallbackRaceID > 0)
1212 {
1213 result = findBestComponentModel(cmfTbl, candidateIds, charInfos.modelFallbackRaceID, charInfos.modelFallbackSexID,
1214 static_cast<int>(GENDER_NONE), isDH, positionDesc);
1215 if (result != 0)
1216 return result;
1217 }
1218
1219 // We still didn't find the model, so check for RACE_ANY (race = 0) items:
1220 // Note: currently all race = 0 entries are also gender = 2, but we probably
1221 // shouldn't assume it will stay that way, so check for both gender values:
1222 result = findBestComponentModel(cmfTbl, candidateIds, static_cast<int>(RACE_ANY), charInfos.modelFallbackSexID,
1223 static_cast<int>(GENDER_NONE), isDH, positionDesc);
1224 if (result != 0)
1225 return result;
1226
1227 return 0;
1228}
1229
1230int WoWItem::getCustomTextureId(size_t index) const
1231{
1232 if (!charModel_)
1233 return 0;
1234
1235 // Step 1: Get ModelMaterialResourcesID from ItemDisplayInfo
1236 const DB2Table* idiTbl = WOWDB.getTable("ItemDisplayInfo");
1237 if (!idiTbl)
1238 return 0;
1239
1240 DB2Row idiRow = idiTbl->getRow(displayId_);
1241 if (!idiRow)
1242 return 0;
1243
1244 const uint32_t matResID = (index == 0) ? idiRow.getUInt("ModelMaterialResourcesID1") : idiRow.getUInt("ModelMaterialResourcesID2");
1245 if (matResID == 0)
1246 return 0;
1247
1248 // Step 2: Find FileDataIDs from TextureFileData where MaterialResourcesID matches
1249 const DB2Table* tfdTbl = WOWDB.getTable("TextureFileData");
1250 if (!tfdTbl)
1251 return 0;
1252
1253 std::set<int> candidateIds;
1254 for (const auto& row : *tfdTbl)
1255 {
1256 if (row.getUInt("MaterialResourcesID") == matResID)
1257 candidateIds.insert(static_cast<int>(row.getUInt("FileDataID")));
1258 }
1259
1260 if (candidateIds.empty())
1261 return 0;
1262
1263 // if there is only one result, return texture id:
1264 if (candidateIds.size() == 1)
1265 return *candidateIds.begin();
1266
1267 // Step 3: Filter through ComponentTextureFileData by race/sex/class
1268 const DB2Table* ctfTbl = WOWDB.getTable("ComponentTextureFileData");
1269 if (!ctfTbl)
1270 return 0;
1271
1272 const auto charInfos = charModel_->infos;
1273 const bool isDH = charModel_->cd.isDemonHunter();
1274
1275 // Order all queries by GenderIndex to ensure definite genders have priority over generic ones,
1276 // and ClassID descending to ensure Demon Hunter textures have priority over regular ones, for DHs only:
1277 int result = findBestComponentTexture(ctfTbl, candidateIds, charInfos.raceID, charInfos.sexID,
1278 static_cast<int>(GENDER_ANY), isDH);
1279 if (result != 0)
1280 return result;
1281
1282 // Failed to find model for that specific race and sex, so check fallback race:
1283 if (charInfos.textureFallbackRaceID > 0)
1284 {
1285 result = findBestComponentTexture(ctfTbl, candidateIds, charInfos.textureFallbackRaceID, charInfos.textureFallbackSexID,
1286 static_cast<int>(GENDER_ANY), isDH);
1287 if (result != 0)
1288 return result;
1289 }
1290
1291 // We still didn't find the model, so check for RACE_ANY (race = 0) items:
1292 result = findBestComponentTexture(ctfTbl, candidateIds, static_cast<int>(RACE_ANY), charInfos.sexID,
1293 static_cast<int>(GENDER_ANY), isDH);
1294 if (result != 0)
1295 return result;
1296
1297 return 0;
1298}
#define GAMEDIRECTORY
Definition Game.h:9
#define LOG_ERROR
Definition Logger.h:11
#define LOG_INFO
Definition Logger.h:10
TextureManager TEXTUREMANAGER
#define WOWDB
Definition WoWDatabase.h:65
static int findBestComponentModel(const DB2Table *cmfTbl, const std::set< int > &candidateIds, int raceID, int sexID, int altGender, bool isDH, bool positionDesc)
Definition WoWItem.cpp:1056
static int findBestComponentTexture(const DB2Table *ctfTbl, const std::set< int > &candidateIds, int raceID, int sexID, int altGender, bool isDH)
Definition WoWItem.cpp:1105
void delSlot(int slot)
Attachment * addChild(std::string fn, int id, int slot)
bool isDemonHunter() const
std::map< uint, uint > geosets
Definition CharDetails.h:97
void addLayer(GameFile *file, int region, int layer, int blendMode=1)
Base class for all scene-graph nodes in the component hierarchy.
Definition Component.h:11
const Component * parent() const
Get the parent component (const).
Definition Component.h:39
std::string name() const
Definition Component.cpp:52
void setName(const std::string &name)
Definition Component.cpp:46
Lightweight handle to a single row in a DB2Table.
Definition DB2Table.h:27
int32_t getInt(const std::string &field, unsigned int arrayIndex=0) const
Definition DB2Table.cpp:26
uint32_t recordID() const
Definition DB2Table.cpp:11
uint32_t getUInt(const std::string &field, unsigned int arrayIndex=0) const
Definition DB2Table.cpp:17
Provides typed, field-name-based access to records in a WDC DB2 file.
Definition DB2Table.h:50
Iterator begin() const
Definition DB2Table.h:78
DB2Row getRow(uint32_t id) const
Definition DB2Table.cpp:97
Attachment * attachment
Definition displayable.h:34
Abstract base class representing a file within the game data archive.
Definition GameFile.h:12
const std::string & fullname() const
Definition GameFile.h:56
const ItemRecord & getById(int id) const
Definition database.cpp:97
void delbyname(const std::string &name)
Definition manager.h:90
GameFile * GetBorderTex(int slot)
GameFile * GetIconTex(int slot)
void load(const pugi::xml_node &node)
void save(pugi::xml_node &parentNode)
GameFile * GetBackgroundTex(int slot)
virtual GLuint add(GameFile *)
std::map< CharGeosets, int > itemGeosets_
Definition WoWItem.h:88
void setDisplayId(int id)
Definition WoWItem.cpp:112
WoWModel * charModel_
Definition WoWItem.h:72
int displayFlags_
Definition WoWItem.h:79
int displayId_
Definition WoWItem.h:75
std::map< int, int > modifierIdDisplayMap_
Definition WoWItem.h:90
int getCustomTextureId(size_t index) const
Definition WoWItem.cpp:1230
WoWItem(CharSlots slot)
Definition WoWItem.cpp:29
CharSlots slot_
Definition WoWItem.h:82
unsigned int nbLevels_
Definition WoWItem.h:80
int id() const
Definition WoWItem.h:44
int level_
Definition WoWItem.h:77
CharRegions getRegionForTexture(GameFile *file) const
Definition WoWItem.cpp:998
void setId(int id)
Definition WoWItem.cpp:34
void load()
Definition WoWItem.cpp:204
bool isCustomizableTabard() const
Definition WoWItem.cpp:863
std::map< int, int > levelDisplayMap_
Definition WoWItem.h:89
int getCustomModelId(size_t index) const
Definition WoWItem.cpp:1152
void save(pugi::xml_node &parentNode) const
Definition WoWItem.cpp:870
int type_
Definition WoWItem.h:78
int quality_
Definition WoWItem.h:76
CharSlots slot() const
Definition WoWItem.h:50
void onParentSet(Component *) override
Called after the parent has been set; override for custom logic.
Definition WoWItem.cpp:166
std::map< POSITION_SLOTS, WoWModel * > models() const
Definition WoWItem.h:62
void refresh()
Definition WoWItem.cpp:603
void mergeModel(CharSlots slot, int modelId, int textureId)
Definition WoWItem.cpp:969
void unload()
Definition WoWItem.cpp:171
WoWModel * mergedModel_
Definition WoWItem.h:92
std::map< CharRegions, GameFile * > itemTextures_
Definition WoWItem.h:87
int id_
Definition WoWItem.h:74
void setModifierId(int id)
Definition WoWItem.cpp:145
void setLevel(int level)
Definition WoWItem.cpp:123
static std::map< CharSlots, int > SLOT_LAYERS_
Definition WoWItem.h:20
std::map< POSITION_SLOTS, WoWModel * > itemModels_
Definition WoWItem.h:91
void updateItemModel(POSITION_SLOTS pos, int modelId, int textureId)
Definition WoWItem.cpp:942
Core WoW .m2 model: geometry, animation, textures, and character data.
Definition WoWModel.h:50
TabardDetails td
Definition WoWModel.h:216
WoWModel * mergeModel(std::string name, int type=1, bool noRefresh=false)
void hideAllGeosets()
RaceInfos infos
Definition WoWModel.h:215
void updateTextureList(GameFile *tex, int special)
bool isWearingARobe()
CharTexture tex
Definition WoWModel.h:194
CharModelDetails charModelDetails
Definition WoWModel.h:213
bool ok
Definition WoWModel.h:166
void unmergeModel(std::string name)
CharDetails cd
Definition WoWModel.h:214
bool bSheathe
Definition WoWModel.h:220
std::vector< ModelGeosetHD * > geosets
Definition WoWModel.h:149
void setGeosetGroupDisplay(CharGeosets group, int val)
ItemDatabase items
Definition database.cpp:7
unsigned int uint
Definition types.h:36
@ GENDER_ANY
Definition wow_enums.h:344
@ GENDER_NONE
Definition wow_enums.h:343
@ IT_SHIELD
Definition wow_enums.h:246
POSITION_SLOTS
Attachment point positions on a character model.
Definition wow_enums.h:80
@ ATT_LEFT_HIP_SHEATH
Definition wow_enums.h:113
@ ATT_HELMET
Definition wow_enums.h:92
@ ATT_LEFT_PALM
Definition wow_enums.h:83
@ ATT_BELT_BUCKLE
Definition wow_enums.h:134
@ ATT_RIGHT_BACK
Definition wow_enums.h:112
@ ATT_LEFT_BACK
Definition wow_enums.h:111
@ ATT_RIGHT_HIP_SHEATH
Definition wow_enums.h:114
@ ATT_LEFT_SHOULDER
Definition wow_enums.h:87
@ ATT_LEFT_WRIST
Definition wow_enums.h:81
@ ATT_RIGHT_PALM
Definition wow_enums.h:82
@ ATT_RIGHT_SHOULDER
Definition wow_enums.h:86
@ ATT_LEFT_BACK_SHEATH
Definition wow_enums.h:108
@ ATT_RIGHT_BACK_SHEATH
Definition wow_enums.h:107
CharRegions
Texture compositing region IDs for character texture layout.
Definition wow_enums.h:141
@ CR_LEG_LOWER
Definition wow_enums.h:149
@ CR_ARM_UPPER
Definition wow_enums.h:143
@ CR_LEG_UPPER
Definition wow_enums.h:148
@ CR_TABARD_6
Definition wow_enums.h:162
@ CR_HAND
Definition wow_enums.h:145
@ CR_TABARD_1
Definition wow_enums.h:157
@ CR_TABARD_4
Definition wow_enums.h:160
@ CR_TABARD_2
Definition wow_enums.h:158
@ CR_UNK8
Definition wow_enums.h:151
@ CR_ARM_LOWER
Definition wow_enums.h:144
@ CR_TORSO_UPPER
Definition wow_enums.h:146
@ CR_CAPE
Definition wow_enums.h:155
@ CR_TABARD_3
Definition wow_enums.h:159
@ CR_TABARD_5
Definition wow_enums.h:161
@ CR_TORSO_LOWER
Definition wow_enums.h:147
@ CR_FOOT
Definition wow_enums.h:150
@ CLASS_DEMONHUNTER
Definition wow_enums.h:414
@ CLASS_ANY
Definition wow_enums.h:402
@ CG_TORSO
Definition wow_enums.h:48
@ CG_KNEEPADS
Definition wow_enums.h:36
@ CG_TROUSERS
Definition wow_enums.h:40
@ CG_BELT
Definition wow_enums.h:44
@ CG_GEOSET2600
Definition wow_enums.h:52
@ CG_GEOSET2700
Definition wow_enums.h:53
@ CG_TABARD
Definition wow_enums.h:39
@ CG_SLEEVES
Definition wow_enums.h:35
@ CG_FEET
Definition wow_enums.h:46
@ CG_CLOAK
Definition wow_enums.h:42
@ CG_GEOSET2100
Definition wow_enums.h:47
@ CG_GLOVES
Definition wow_enums.h:31
@ CG_HAND_ATTACHMENT
Definition wow_enums.h:49
@ CG_PANTS
Definition wow_enums.h:38
@ CG_GEOSET2800
Definition wow_enums.h:54
@ CG_BOOTS
Definition wow_enums.h:32
@ CG_CHEST
Definition wow_enums.h:37
CharSlots
Character equipment slot indices.
Definition wow_enums.h:5
@ CS_BRACERS
Definition wow_enums.h:13
@ CS_QUIVER
Definition wow_enums.h:19
@ CS_HAND_LEFT
Definition wow_enums.h:16
@ CS_BELT
Definition wow_enums.h:9
@ CS_CAPE
Definition wow_enums.h:17
@ CS_SHOULDER
Definition wow_enums.h:7
@ CS_PANTS
Definition wow_enums.h:11
@ CS_TABARD
Definition wow_enums.h:18
@ CS_CHEST
Definition wow_enums.h:12
@ CS_HAND_RIGHT
Definition wow_enums.h:15
@ CS_HEAD
Definition wow_enums.h:6
@ CS_BOOTS
Definition wow_enums.h:8
@ CS_SHIRT
Definition wow_enums.h:10
@ CS_GLOVES
Definition wow_enums.h:14
@ SHEATHETYPE_NONE
Definition wow_enums.h:222
@ RACE_ANY
Definition wow_enums.h:350
@ TEXTURE_OBJECT_SKIN
Definition wow_enums.h:312