aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/server/game/DataStores/DB2Stores.cpp45
-rw-r--r--src/server/game/DataStores/DBCEnums.h26
-rw-r--r--src/server/game/Entities/Player/Player.cpp177
-rw-r--r--src/server/game/Entities/Player/Player.h2
-rw-r--r--src/server/game/Handlers/CharacterHandler.cpp18
5 files changed, 203 insertions, 65 deletions
diff --git a/src/server/game/DataStores/DB2Stores.cpp b/src/server/game/DataStores/DB2Stores.cpp
index dfae9795b4e..2ef4a85c679 100644
--- a/src/server/game/DataStores/DB2Stores.cpp
+++ b/src/server/game/DataStores/DB2Stores.cpp
@@ -472,9 +472,50 @@ void DB2Manager::LoadStores(std::string const& dataPath, uint32 defaultLocale)
for (AreaGroupMemberEntry const* areaGroupMember : sAreaGroupMemberStore)
_areaGroupMembers[areaGroupMember->AreaGroupID].push_back(areaGroupMember->AreaID);
+ std::unordered_map<uint32, std::set<std::pair<uint8, uint8>>> addedSections;
for (CharSectionsEntry const* charSection : sCharSectionsStore)
- if (charSection->Race && ((1 << (charSection->Race - 1)) & RACEMASK_ALL_PLAYABLE) != 0) //ignore Nonplayable races
- _charSections.insert({ charSection->GenType | (charSection->Gender << 8) | (charSection->Race << 16), charSection });
+ {
+ if (!charSection->Race || !((1 << (charSection->Race - 1)) & RACEMASK_ALL_PLAYABLE)) //ignore Nonplayable races
+ continue;
+
+ // Not all sections are used for low-res models but we need to get all sections for validation since its viewer dependent
+ uint8 baseSection = charSection->GenType;
+ switch (baseSection)
+ {
+ case SECTION_TYPE_SKIN_LOW_RES:
+ case SECTION_TYPE_FACE_LOW_RES:
+ case SECTION_TYPE_FACIAL_HAIR_LOW_RES:
+ case SECTION_TYPE_HAIR_LOW_RES:
+ case SECTION_TYPE_UNDERWEAR_LOW_RES:
+ baseSection = baseSection + SECTION_TYPE_SKIN;
+ break;
+ case SECTION_TYPE_SKIN:
+ case SECTION_TYPE_FACE:
+ case SECTION_TYPE_FACIAL_HAIR:
+ case SECTION_TYPE_HAIR:
+ case SECTION_TYPE_UNDERWEAR:
+ break;
+ case SECTION_TYPE_CUSTOM_DISPLAY_1_LOW_RES:
+ case SECTION_TYPE_CUSTOM_DISPLAY_2_LOW_RES:
+ case SECTION_TYPE_CUSTOM_DISPLAY_3_LOW_RES:
+ ++baseSection;
+ break;
+ case SECTION_TYPE_CUSTOM_DISPLAY_1:
+ case SECTION_TYPE_CUSTOM_DISPLAY_2:
+ case SECTION_TYPE_CUSTOM_DISPLAY_3:
+ break;
+ default:
+ break;
+ }
+
+ uint32 sectionKey = baseSection | (charSection->Gender << 8) | (charSection->Race << 16);
+ std::pair<uint8, uint8> sectionCombination{ charSection->Type, charSection->Color };
+ if (addedSections[sectionKey].count(sectionCombination))
+ continue;
+
+ addedSections[sectionKey].insert(sectionCombination);
+ _charSections.insert({ sectionKey, charSection });
+ }
for (CharStartOutfitEntry const* outfit : sCharStartOutfitStore)
_charStartOutfits[outfit->RaceID | (outfit->ClassID << 8) | (outfit->GenderID << 16)] = outfit;
diff --git a/src/server/game/DataStores/DBCEnums.h b/src/server/game/DataStores/DBCEnums.h
index b00cbf7e9fb..50eeeaa7ad8 100644
--- a/src/server/game/DataStores/DBCEnums.h
+++ b/src/server/game/DataStores/DBCEnums.h
@@ -417,19 +417,27 @@ enum CharSectionFlags
{
SECTION_FLAG_PLAYER = 0x01,
SECTION_FLAG_DEATH_KNIGHT = 0x04,
- SECTION_FLAG_DEMON_HUNTER = 0x40
+ SECTION_FLAG_DEMON_HUNTER = 0x20
};
enum CharSectionType
{
- SECTION_TYPE_SKIN = 0,
- SECTION_TYPE_FACE = 1,
- SECTION_TYPE_FACIAL_HAIR = 2,
- SECTION_TYPE_HAIR = 3,
- SECTION_TYPE_UNDERWEAR = 4,
- SECTION_TYPE_CUSTOM_DISPLAY_1 = 10,
- SECTION_TYPE_CUSTOM_DISPLAY_2 = 12,
- SECTION_TYPE_CUSTOM_DISPLAY_3 = 14
+ SECTION_TYPE_SKIN_LOW_RES = 0,
+ SECTION_TYPE_FACE_LOW_RES = 1,
+ SECTION_TYPE_FACIAL_HAIR_LOW_RES = 2,
+ SECTION_TYPE_HAIR_LOW_RES = 3,
+ SECTION_TYPE_UNDERWEAR_LOW_RES = 4,
+ SECTION_TYPE_SKIN = 5,
+ SECTION_TYPE_FACE = 6,
+ SECTION_TYPE_FACIAL_HAIR = 7,
+ SECTION_TYPE_HAIR = 8,
+ SECTION_TYPE_UNDERWEAR = 9,
+ SECTION_TYPE_CUSTOM_DISPLAY_1_LOW_RES = 10,
+ SECTION_TYPE_CUSTOM_DISPLAY_1 = 11,
+ SECTION_TYPE_CUSTOM_DISPLAY_2_LOW_RES = 12,
+ SECTION_TYPE_CUSTOM_DISPLAY_2 = 13,
+ SECTION_TYPE_CUSTOM_DISPLAY_3_LOW_RES = 14,
+ SECTION_TYPE_CUSTOM_DISPLAY_3 = 15
};
enum Difficulty : uint8
diff --git a/src/server/game/Entities/Player/Player.cpp b/src/server/game/Entities/Player/Player.cpp
index 9626b828cf9..2ceca5d5ed0 100644
--- a/src/server/game/Entities/Player/Player.cpp
+++ b/src/server/game/Entities/Player/Player.cpp
@@ -23939,15 +23939,25 @@ bool Player::CanCaptureTowerPoint() const
IsAlive()); // live player
}
-uint32 Player::GetBarberShopCost(BarberShopStyleEntry const* newHairStyle, uint8 newHairColor, BarberShopStyleEntry const* newFacialHair, BarberShopStyleEntry const* newSkin /*= nullptr*/, BarberShopStyleEntry const* newFace /*= nullptr*/) const
+uint32 Player::GetBarberShopCost(BarberShopStyleEntry const* newHairStyle, uint8 newHairColor, BarberShopStyleEntry const* newFacialHair, BarberShopStyleEntry const* newSkin, BarberShopStyleEntry const* newFace, std::array<BarberShopStyleEntry const*, PLAYER_CUSTOM_DISPLAY_SIZE> const& newCustomDisplay) const
{
uint8 hairstyle = GetByteValue(PLAYER_BYTES, PLAYER_BYTES_OFFSET_HAIR_STYLE_ID);
uint8 haircolor = GetByteValue(PLAYER_BYTES, PLAYER_BYTES_OFFSET_HAIR_COLOR_ID);
uint8 facialhair = GetByteValue(PLAYER_BYTES_2, PLAYER_BYTES_2_OFFSET_FACIAL_STYLE);
uint8 skincolor = GetByteValue(PLAYER_BYTES, PLAYER_BYTES_OFFSET_SKIN_ID);
uint8 face = GetByteValue(PLAYER_BYTES, PLAYER_BYTES_OFFSET_FACE_ID);
-
- if ((hairstyle == newHairStyle->Data) && (haircolor == newHairColor) && (facialhair == newFacialHair->Data) && (!newSkin || (newSkin->Data == skincolor)) && (!newFace || (newFace->Data == face)))
+ std::array<uint8, PLAYER_CUSTOM_DISPLAY_SIZE> customDisplay;
+ for (uint32 i = 0; i < PLAYER_CUSTOM_DISPLAY_SIZE; ++i)
+ customDisplay[i] = GetByteValue(PLAYER_BYTES_2, PLAYER_BYTES_2_OFFSET_CUSTOM_DISPLAY_OPTION + i);
+
+ if ((hairstyle == newHairStyle->Data) &&
+ (haircolor == newHairColor) &&
+ (facialhair == newFacialHair->Data) &&
+ (!newSkin || (newSkin->Data == skincolor)) &&
+ (!newFace || (newFace->Data == face)) &&
+ (!newCustomDisplay[0] || (newCustomDisplay[0]->Data == customDisplay[0])) &&
+ (!newCustomDisplay[1] || (newCustomDisplay[1]->Data == customDisplay[1])) &&
+ (!newCustomDisplay[2] || (newCustomDisplay[2]->Data == customDisplay[2])))
return 0;
GtBarberShopCostBaseEntry const* bsc = sBarberShopCostBaseGameTable.GetRow(getLevel());
@@ -23971,6 +23981,10 @@ uint32 Player::GetBarberShopCost(BarberShopStyleEntry const* newHairStyle, uint8
if (newFace && face != newFace->Data)
cost += uint32(bsc->Cost * newFace->CostModifier);
+ for (uint32 i = 0; i < PLAYER_CUSTOM_DISPLAY_SIZE; ++i)
+ if (newCustomDisplay[i] && customDisplay[i] != newCustomDisplay[i]->Data)
+ cost += uint32(bsc->Cost * newCustomDisplay[i]->CostModifier);
+
return cost;
}
@@ -26065,65 +26079,128 @@ void Player::RemoveSocial()
m_social = nullptr;
}
-bool IsSectionFlagValid(CharSectionsEntry const* entry, uint8 class_, bool create)
+uint32 GetSelectionFromContext(uint32 context, uint32 playerClass)
{
- if (create && !(entry->Flags & SECTION_FLAG_PLAYER))
- return false;
-
- if (class_ != CLASS_DEATH_KNIGHT && (entry->Flags & SECTION_FLAG_DEATH_KNIGHT))
- return false;
-
- if (class_ != CLASS_DEMON_HUNTER && (entry->Flags & SECTION_FLAG_DEMON_HUNTER))
- return false;
+ switch (context)
+ {
+ case 1:
+ if (playerClass == CLASS_DEATH_KNIGHT)
+ return 1;
+ if (playerClass == CLASS_DEMON_HUNTER)
+ return 3;
+ return 0;
+ case 2:
+ if (playerClass == CLASS_DEATH_KNIGHT)
+ return 5;
+ if (playerClass == CLASS_DEMON_HUNTER)
+ return 6;
+ return 4;
+ case 3:
+ return 7;
+ case 4:
+ if (playerClass == CLASS_DEATH_KNIGHT)
+ return 9;
+ if (playerClass == CLASS_DEMON_HUNTER)
+ return 10;
+ return 8;
+ default:
+ if (playerClass == CLASS_DEATH_KNIGHT)
+ return 1;
+ if (playerClass == CLASS_DEMON_HUNTER)
+ return 2;
+ return 0;
+ }
- return true;
+ return 0;
}
-bool Player::ValidateAppearance(uint8 race, uint8 class_, uint8 gender, uint8 hairID, uint8 hairColor, uint8 faceID, uint8 facialHair, uint8 skinColor, std::array<uint8, PLAYER_CUSTOM_DISPLAY_SIZE> const& customDisplay, bool create /*= false*/)
+bool ComponentFlagsMatch(CharSectionsEntry const* entry, uint32 selection)
{
- // Check skin color
- // For Skin type is always 0
- if (CharSectionsEntry const* entry = sDB2Manager.GetCharSectionEntry(race, SECTION_TYPE_SKIN, gender, 0, skinColor))
- { // Skin Color defined as Face color, too, we check skin & face in one pass
- if (CharSectionsEntry const* entry2 = sDB2Manager.GetCharSectionEntry(race, SECTION_TYPE_FACE, gender, faceID, skinColor))
- {
- if (!IsSectionFlagValid(entry, class_, create))
+ switch (selection)
+ {
+ case 0:
+ if (!(entry->Flags & 1))
return false;
- if (!IsSectionFlagValid(entry2, class_, create))
+ return !(entry->Flags & 0x2C);
+ case 1:
+ if (!(entry->Flags & 1))
return false;
- }
- else
- return false;
+ if (!(entry->Flags & 0x94))
+ return false;
+ return !(entry->Flags & 8);
+ case 2:
+ if (!(entry->Flags & 1))
+ return false;
+ if (!(entry->Flags & 0x70))
+ return false;
+ return !(entry->Flags & 8);
+ case 3:
+ if (!(entry->Flags & 1))
+ return false;
+ if (!(entry->Flags & 0x20))
+ return false;
+ return !(entry->Flags & 8);
+ case 4:
+ case 8:
+ if (!(entry->Flags & 3))
+ return false;
+ return !(entry->Flags & 0x2C);
+ case 5:
+ case 9:
+ if (!(entry->Flags & 3))
+ return false;
+ if (!(entry->Flags & 0x94))
+ return false;
+ return !(entry->Flags & 8);
+ case 6:
+ case 10:
+ if (!(entry->Flags & 3))
+ return false;
+ if (!(entry->Flags & 0x70))
+ return false;
+ return !(entry->Flags & 8);
+ case 7:
+ return true;
+ default:
+ break;
}
- else
+
+ return false;
+}
+
+bool IsSectionFlagValid(CharSectionsEntry const* entry, uint8 class_, bool create)
+{
+ if (create)
+ return ComponentFlagsMatch(entry, GetSelectionFromContext(0, class_));
+
+ return ComponentFlagsMatch(entry, GetSelectionFromContext(2, class_));
+}
+
+bool Player::ValidateAppearance(uint8 race, uint8 class_, uint8 gender, uint8 hairID, uint8 hairColor, uint8 faceID, uint8 facialHairID, uint8 skinColor, std::array<uint8, PLAYER_CUSTOM_DISPLAY_SIZE> const& customDisplay, bool create /*= false*/)
+{
+ CharSectionsEntry const* skin = sDB2Manager.GetCharSectionEntry(race, SECTION_TYPE_SKIN, gender, 0, skinColor);
+ if (!skin)
return false;
- // These combinations don't have an entry of Type SECTION_TYPE_FACIAL_HAIR, exclude them from that check
- bool excludeCheck = (race == RACE_TAUREN) || (race == RACE_DRAENEI) || (gender == GENDER_FEMALE && race != RACE_NIGHTELF && race != RACE_UNDEAD_PLAYER);
+ if (!IsSectionFlagValid(skin, class_, create))
+ return false;
- // Check Hair
- if (CharSectionsEntry const* entry = sDB2Manager.GetCharSectionEntry(race, SECTION_TYPE_HAIR, gender, hairID, hairColor))
- {
- if (!IsSectionFlagValid(entry, class_, create))
- return false;
+ CharSectionsEntry const* face = sDB2Manager.GetCharSectionEntry(race, SECTION_TYPE_FACE, gender, faceID, skinColor);
+ if (!face)
+ return false;
- if (!excludeCheck)
- {
- if (CharSectionsEntry const* entry2 = sDB2Manager.GetCharSectionEntry(race, SECTION_TYPE_FACIAL_HAIR, gender, facialHair, hairColor))
- {
- if (!IsSectionFlagValid(entry2, class_, create))
- return false;
- }
- else
- return false;
- }
- else
- {
- // @TODO: Bound checking for facialHair ID (used clientside for markings, tauren beard, etc.)
- // Not present in DBC
- }
- }
- else
+ if (!IsSectionFlagValid(face, class_, create))
+ return false;
+
+ CharSectionsEntry const* hair = sDB2Manager.GetCharSectionEntry(race, SECTION_TYPE_HAIR, gender, hairID, hairColor);
+ if (!hair)
+ return false;
+
+ if (!IsSectionFlagValid(hair, class_, create))
+ return false;
+
+ CharSectionsEntry const* facialHair = sDB2Manager.GetCharSectionEntry(race, SECTION_TYPE_HAIR, gender, facialHairID, hairColor);
+ if (!facialHair)
return false;
for (uint32 i = 0; i < PLAYER_CUSTOM_DISPLAY_SIZE; ++i)
diff --git a/src/server/game/Entities/Player/Player.h b/src/server/game/Entities/Player/Player.h
index 7edc88bdecc..a038d00f939 100644
--- a/src/server/game/Entities/Player/Player.h
+++ b/src/server/game/Entities/Player/Player.h
@@ -1235,7 +1235,7 @@ class TC_GAME_API Player : public Unit, public GridObject<Player>
uint8 GetChatFlags() const;
std::string autoReplyMsg;
- uint32 GetBarberShopCost(BarberShopStyleEntry const* newHairStyle, uint8 newHairColor, BarberShopStyleEntry const* newFacialHair, BarberShopStyleEntry const* newSkin = nullptr, BarberShopStyleEntry const* newFace = nullptr) const;
+ uint32 GetBarberShopCost(BarberShopStyleEntry const* newHairStyle, uint8 newHairColor, BarberShopStyleEntry const* newFacialHair, BarberShopStyleEntry const* newSkin, BarberShopStyleEntry const* newFace, std::array<BarberShopStyleEntry const*, PLAYER_CUSTOM_DISPLAY_SIZE> const& newCustomDisplay) const;
PlayerSocial* GetSocial() const { return m_social; }
void RemoveSocial();
diff --git a/src/server/game/Handlers/CharacterHandler.cpp b/src/server/game/Handlers/CharacterHandler.cpp
index cd9b8ed2cf6..b7c500cccc9 100644
--- a/src/server/game/Handlers/CharacterHandler.cpp
+++ b/src/server/game/Handlers/CharacterHandler.cpp
@@ -1408,13 +1408,25 @@ void WorldSession::HandleAlterAppearance(WorldPackets::Character::AlterApperance
if (bs_face && (bs_face->Type != 4 || bs_face->Race != _player->getRace() || bs_face->Sex != _player->GetByteValue(PLAYER_BYTES_3, PLAYER_BYTES_3_OFFSET_GENDER)))
return;
+ std::array<BarberShopStyleEntry const*, PLAYER_CUSTOM_DISPLAY_SIZE> customDisplayEntries;
+ std::array<uint8, PLAYER_CUSTOM_DISPLAY_SIZE> customDisplay;
+ for (std::size_t i = 0; i < PLAYER_CUSTOM_DISPLAY_SIZE; ++i)
+ {
+ BarberShopStyleEntry const* bs_customDisplay = sBarberShopStyleStore.LookupEntry(packet.NewCustomDisplay[i]);
+ if (bs_customDisplay && (bs_customDisplay->Type != 5 + i || bs_customDisplay->Race != _player->getRace() || bs_customDisplay->Sex != _player->GetByteValue(PLAYER_BYTES_3, PLAYER_BYTES_3_OFFSET_GENDER)))
+ return;
+
+ customDisplayEntries[i] = bs_customDisplay;
+ customDisplay[i] = bs_customDisplay->Data;
+ }
+
if (!Player::ValidateAppearance(_player->getRace(), _player->getClass(), _player->GetByteValue(PLAYER_BYTES_3, PLAYER_BYTES_3_OFFSET_GENDER),
bs_hair->Data,
packet.NewHairColor,
bs_face ? bs_face->Data : _player->GetByteValue(PLAYER_BYTES, PLAYER_BYTES_OFFSET_FACE_ID),
bs_facialHair->Data,
bs_skinColor ? bs_skinColor->Data : _player->GetByteValue(PLAYER_BYTES, PLAYER_BYTES_OFFSET_SKIN_ID),
- std::array<uint8, PLAYER_CUSTOM_DISPLAY_SIZE>{0, 0, 0}))
+ customDisplay))
return;
GameObject* go = _player->FindNearestGameObjectOfType(GAMEOBJECT_TYPE_BARBER_CHAIR, 5.0f);
@@ -1430,7 +1442,7 @@ void WorldSession::HandleAlterAppearance(WorldPackets::Character::AlterApperance
return;
}
- uint32 cost = _player->GetBarberShopCost(bs_hair, packet.NewHairColor, bs_facialHair, bs_skinColor, bs_face);
+ uint32 cost = _player->GetBarberShopCost(bs_hair, packet.NewHairColor, bs_facialHair, bs_skinColor, bs_face, customDisplayEntries);
// 0 - ok
// 1, 3 - not enough money
@@ -1495,7 +1507,7 @@ void WorldSession::HandleCharCustomizeCallback(PreparedQueryResult result, World
uint16 atLoginFlags = fields[4].GetUInt16();
if (!Player::ValidateAppearance(plrRace, plrClass, plrGender, customizeInfo->HairStyleID, customizeInfo->HairColorID, customizeInfo->FaceID,
- customizeInfo->FacialHairStyleID, customizeInfo->SkinID, customizeInfo->CustomDisplay, true))
+ customizeInfo->FacialHairStyleID, customizeInfo->SkinID, customizeInfo->CustomDisplay))
{
SendCharCustomize(CHAR_CREATE_ERROR, customizeInfo);
return;