diff options
Diffstat (limited to 'src')
-rw-r--r-- | src/server/game/DataStores/DB2Stores.cpp | 45 | ||||
-rw-r--r-- | src/server/game/DataStores/DBCEnums.h | 26 | ||||
-rw-r--r-- | src/server/game/Entities/Player/Player.cpp | 177 | ||||
-rw-r--r-- | src/server/game/Entities/Player/Player.h | 2 | ||||
-rw-r--r-- | src/server/game/Handlers/CharacterHandler.cpp | 18 |
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; |