diff --git a/common/emu_oplist.h b/common/emu_oplist.h index da78e4e02..2af98de1c 100644 --- a/common/emu_oplist.h +++ b/common/emu_oplist.h @@ -455,6 +455,7 @@ N(OP_ServerListResponse), N(OP_SessionReady), N(OP_SetChatServer), N(OP_SetChatServer2), +N(OP_SetFace), N(OP_SetGroupTarget), N(OP_SetGuildMOTD), N(OP_SetGuildRank), diff --git a/common/eq_packet_structs.h b/common/eq_packet_structs.h index 76699c3d1..47bfabb23 100644 --- a/common/eq_packet_structs.h +++ b/common/eq_packet_structs.h @@ -2316,9 +2316,12 @@ struct FaceChange_Struct { /*004*/ uint8 hairstyle; /*005*/ uint8 beard; /*006*/ uint8 face; -/*007*/ uint32 drakkin_heritage; -/*011*/ uint32 drakkin_tattoo; -/*015*/ uint32 drakkin_details; +/*007*/ uint8 unused_padding; +/*008*/ uint32 drakkin_heritage; +/*012*/ uint32 drakkin_tattoo; +/*016*/ uint32 drakkin_details; +/*020*/ uint32 entity_id; +/*024*/ //there are only 10 faces for barbs changing woad just //increase the face value by ten so if there were 8 woad //designs then there would be 80 barb faces diff --git a/common/patches/rof2_structs.h b/common/patches/rof2_structs.h index 2c3445a46..42c1e5883 100644 --- a/common/patches/rof2_structs.h +++ b/common/patches/rof2_structs.h @@ -2649,11 +2649,11 @@ struct FaceChange_Struct { /*004*/ uint8 hairstyle; /*005*/ uint8 beard; /*006*/ uint8 face; -/*007*/ uint8 unknown007; +/*007*/ uint8 unused_padding; /*008*/ uint32 drakkin_heritage; /*012*/ uint32 drakkin_tattoo; /*016*/ uint32 drakkin_details; -/*020*/ uint32 unknown020; +/*020*/ uint32 entity_id; /*024*/ }; //there are only 10 faces for barbs changing woad just diff --git a/common/patches/rof_structs.h b/common/patches/rof_structs.h index e068e94a9..662cc3872 100644 --- a/common/patches/rof_structs.h +++ b/common/patches/rof_structs.h @@ -2622,11 +2622,11 @@ struct FaceChange_Struct { /*004*/ uint8 hairstyle; /*005*/ uint8 beard; /*006*/ uint8 face; -/*007*/ uint8 unknown007; +/*007*/ uint8 unused_padding; /*008*/ uint32 drakkin_heritage; /*012*/ uint32 drakkin_tattoo; /*016*/ uint32 drakkin_details; -/*020*/ uint32 unknown020; +/*020*/ uint32 entity_id; /*024*/ }; //there are only 10 faces for barbs changing woad just diff --git a/common/patches/sod_structs.h b/common/patches/sod_structs.h index c39b5b65f..7b0e45e46 100644 --- a/common/patches/sod_structs.h +++ b/common/patches/sod_structs.h @@ -2136,11 +2136,11 @@ struct FaceChange_Struct { /*004*/ uint8 hairstyle; /*005*/ uint8 beard; /*006*/ uint8 face; -/*007*/ uint8 unknown007; +/*007*/ uint8 unused_padding; /*008*/ uint32 drakkin_heritage; /*012*/ uint32 drakkin_tattoo; /*016*/ uint32 drakkin_details; -/*020*/ uint32 unknown020; +/*020*/ uint32 entity_id; /*024*/ }; //there are only 10 faces for barbs changing woad just diff --git a/common/patches/sof_structs.h b/common/patches/sof_structs.h index d750d24ad..05dfba0c5 100644 --- a/common/patches/sof_structs.h +++ b/common/patches/sof_structs.h @@ -2106,11 +2106,11 @@ struct FaceChange_Struct { /*004*/ uint8 hairstyle; /*005*/ uint8 beard; /*006*/ uint8 face; -/*007*/ uint8 unknown007; +/*007*/ uint8 unused_padding; /*008*/ uint32 drakkin_heritage; /*012*/ uint32 drakkin_tattoo; /*016*/ uint32 drakkin_details; -/*020*/ uint32 unknown020; +/*020*/ uint32 entity_id; /*024*/ }; //there are only 10 faces for barbs changing woad just diff --git a/common/patches/titanium.cpp b/common/patches/titanium.cpp index 25ef3be5c..4c9339854 100644 --- a/common/patches/titanium.cpp +++ b/common/patches/titanium.cpp @@ -1464,6 +1464,30 @@ namespace Titanium FINISH_ENCODE(); } + ENCODE(OP_SetFace) + { + auto emu = reinterpret_cast((*p)->pBuffer); + + EQApplicationPacket outapp(OP_Illusion, sizeof(structs::Illusion_Struct)); + auto buf = reinterpret_cast(outapp.pBuffer); + + buf->spawnid = emu->entity_id; + buf->race = -1; // unchanged + buf->gender = -1; // unchanged + buf->texture = -1; // unchanged + buf->helmtexture = -1; // unchanged + buf->face = emu->face; + buf->hairstyle = emu->hairstyle; + buf->haircolor = emu->haircolor; + buf->beard = emu->beard; + buf->beardcolor = emu->beardcolor; + buf->size = 0.0f; // unchanged + + safe_delete(*p); // not using the original packet + + dest->QueuePacket(&outapp, ack_req); + } + ENCODE(OP_ShopPlayerSell) { ENCODE_LENGTH_EXACT(Merchant_Purchase_Struct); diff --git a/common/patches/titanium_ops.h b/common/patches/titanium_ops.h index c7557a32f..6f11063be 100644 --- a/common/patches/titanium_ops.h +++ b/common/patches/titanium_ops.h @@ -66,6 +66,7 @@ E(OP_ReadBook) E(OP_RespondAA) E(OP_SendCharInfo) E(OP_SendAATable) +E(OP_SetFace) E(OP_ShopPlayerSell) E(OP_SpecialMesg) E(OP_TaskDescription) diff --git a/common/patches/titanium_structs.h b/common/patches/titanium_structs.h index cf918717a..1220877d5 100644 --- a/common/patches/titanium_structs.h +++ b/common/patches/titanium_structs.h @@ -1761,8 +1761,7 @@ struct AdventureRequestResponse_Struct{ struct Illusion_Struct { /*000*/ uint32 spawnid; /*004*/ char charname[64]; -/*068*/ uint16 race; -/*070*/ char unknown070[2]; +/*068*/ int race; /*072*/ uint8 gender; /*073*/ uint8 texture; /*074*/ uint8 helmtexture; diff --git a/common/patches/uf_structs.h b/common/patches/uf_structs.h index a0ee2ba8d..a4b5b510c 100644 --- a/common/patches/uf_structs.h +++ b/common/patches/uf_structs.h @@ -2185,11 +2185,11 @@ struct FaceChange_Struct { /*004*/ uint8 hairstyle; /*005*/ uint8 beard; /*006*/ uint8 face; -/*007*/ uint8 unknown007; +/*007*/ uint8 unused_padding; /*008*/ uint32 drakkin_heritage; /*012*/ uint32 drakkin_tattoo; /*016*/ uint32 drakkin_details; -/*020*/ uint32 unknown020; +/*020*/ uint32 entity_id; /*024*/ }; //there are only 10 faces for barbs changing woad just diff --git a/utils/patches/patch_RoF2.conf b/utils/patches/patch_RoF2.conf index 8bcbfd2ca..8f7d63b39 100644 --- a/utils/patches/patch_RoF2.conf +++ b/utils/patches/patch_RoF2.conf @@ -203,6 +203,7 @@ OP_DeleteSpell=0x3358 OP_Surname=0x0423 OP_ClearSurname=0x3fb0 OP_FaceChange=0x5578 +OP_SetFace=0x1af3 OP_SenseHeading=0x260a OP_Action=0x744c OP_ConsiderCorpse=0x5204 diff --git a/utils/patches/patch_SoD.conf b/utils/patches/patch_SoD.conf index 69f4c996b..5cb5d2a30 100644 --- a/utils/patches/patch_SoD.conf +++ b/utils/patches/patch_SoD.conf @@ -201,6 +201,7 @@ OP_DeleteSpell=0x0736 # C OP_Surname=0x7547 # C OP_ClearSurname=0x2edd # C OP_FaceChange=0x5658 # C +OP_SetFace=0x210a OP_SenseHeading=0x3887 # C OP_Action=0x2c27 # C OP_ConsiderCorpse=0x37a7 # C diff --git a/utils/patches/patch_SoF.conf b/utils/patches/patch_SoF.conf index 56602d8f6..874c91cc3 100644 --- a/utils/patches/patch_SoF.conf +++ b/utils/patches/patch_SoF.conf @@ -198,6 +198,7 @@ OP_DeleteSpell=0x6D7E #Xinu 02/20/09 OP_Surname=0x683E #Xinu 02/21/09 OP_ClearSurname=0x2613 OP_FaceChange=0x482D #Trevius 01/16/09 +OP_SetFace=0x49dc OP_SenseHeading=0x1237 #Trevius 01/16/09 OP_Action=0x5285 #Trevius 01/16/09 OP_ConsiderCorpse=0x4CBB #Xinu 02/20/09 diff --git a/utils/patches/patch_UF.conf b/utils/patches/patch_UF.conf index 6a98e3b02..8c95cbe67 100644 --- a/utils/patches/patch_UF.conf +++ b/utils/patches/patch_UF.conf @@ -205,6 +205,7 @@ OP_DeleteSpell=0x0698 # C OP_Surname=0x44ae # C OP_ClearSurname=0x6705 # C OP_FaceChange=0x37a7 # C +OP_SetFace=0x6cfa OP_SenseHeading=0x1b8a # C OP_Action=0x0f14 # C OP_ConsiderCorpse=0x0a18 # C diff --git a/zone/client_packet.cpp b/zone/client_packet.cpp index eeae9a4f9..ee66f02b2 100644 --- a/zone/client_packet.cpp +++ b/zone/client_packet.cpp @@ -5856,10 +5856,11 @@ void Client::Handle_OP_FaceChange(const EQApplicationPacket *app) return; } - // Notify other clients in zone - entity_list.QueueClients(this, app, false); + auto fc = reinterpret_cast(app->pBuffer); + + // Notify other clients in zone + SetFaceAppearance(*fc, true); - FaceChange_Struct* fc = (FaceChange_Struct*)app->pBuffer; m_pp.haircolor = fc->haircolor; m_pp.beardcolor = fc->beardcolor; m_pp.eyecolor1 = fc->eyecolor1; @@ -5872,7 +5873,6 @@ void Client::Handle_OP_FaceChange(const EQApplicationPacket *app) m_pp.drakkin_details = fc->drakkin_details; Save(); MessageString(Chat::Red, FACE_ACCEPTED); - //Message(Chat::Red, "Facial features updated."); return; } diff --git a/zone/gm_commands/feature.cpp b/zone/gm_commands/feature.cpp index 60ab3a4a4..83d0adb1c 100644 --- a/zone/gm_commands/feature.cpp +++ b/zone/gm_commands/feature.cpp @@ -3,7 +3,7 @@ void command_feature(Client *c, const Seperator *sep) { int arguments = sep->argnum; - if (arguments < 2) { + if (arguments < 2 || !sep->IsNumber(2)) { auto feature_save_link = EQ::SayLinkEngine::GenerateQuestSaylink( "#npcedit featuresave", false, @@ -13,6 +13,7 @@ void command_feature(Client *c, const Seperator *sep) c->Message(Chat::White, "Usage: #feature beard [Beard] - Change your or your target's Beard"); c->Message(Chat::White, "Usage: #feature beardcolor [Beard Color] - Change your or your target's Beard Color"); c->Message(Chat::White, "Usage: #feature details [Details] - Change your or your target's Drakkin Details"); + c->Message(Chat::White, "Usage: #feature eyes [Eye Color] - Change your or your target's Eyes"); c->Message(Chat::White, "Usage: #feature face [Face] - Change your or your target's Face"); c->Message(Chat::White, "Usage: #feature gender [Gender] - Change your or your target's Gender"); c->Message(Chat::White, "Usage: #feature hair [Hair] - Change your or your target's Hair"); @@ -38,6 +39,7 @@ void command_feature(Client *c, const Seperator *sep) bool is_beard = !strcasecmp(sep->arg[1], "beard"); bool is_beard_color = !strcasecmp(sep->arg[1], "beardcolor"); bool is_details = !strcasecmp(sep->arg[1], "details"); + bool is_eyes = !strcasecmp(sep->arg[1], "eyes"); bool is_face = !strcasecmp(sep->arg[1], "face"); bool is_gender = !strcasecmp(sep->arg[1], "gender"); bool is_hair = !strcasecmp(sep->arg[1], "hair"); @@ -53,6 +55,7 @@ void command_feature(Client *c, const Seperator *sep) !is_beard && !is_beard_color && !is_details && + !is_eyes && !is_face && !is_gender && !is_hair && @@ -73,6 +76,7 @@ void command_feature(Client *c, const Seperator *sep) c->Message(Chat::White, "Usage: #feature beard [Beard] - Change your or your target's Beard"); c->Message(Chat::White, "Usage: #feature beardcolor [Beard Color] - Change your or your target's Beard Color"); c->Message(Chat::White, "Usage: #feature details [Details] - Change your or your target's Drakkin Details"); + c->Message(Chat::White, "Usage: #feature eyes [Eye Color] - Change your or your target's Eyes"); c->Message(Chat::White, "Usage: #feature face [Face] - Change your or your target's Face"); c->Message(Chat::White, "Usage: #feature gender [Gender] - Change your or your target's Gender"); c->Message(Chat::White, "Usage: #feature hair [Hair] - Change your or your target's Hair"); @@ -93,57 +97,64 @@ void command_feature(Client *c, const Seperator *sep) return; } - auto beard = target->GetBeard(); - auto beard_color = target->GetBeardColor(); - auto details = target->GetDrakkinDetails(); - auto face = target->GetLuclinFace(); + FaceChange_Struct face{}; + face.haircolor = target->GetHairColor(); + face.beardcolor = target->GetBeardColor(); + face.eyecolor1 = target->GetEyeColor1(); + face.eyecolor2 = target->GetEyeColor2(); + face.hairstyle = target->GetHairStyle(); + face.face = target->GetLuclinFace(); + face.beard = target->GetBeard(); + face.drakkin_heritage = target->GetDrakkinHeritage(); + face.drakkin_tattoo = target->GetDrakkinTattoo(); + face.drakkin_details = target->GetDrakkinDetails(); + auto gender = target->GetGender(); - auto hair = target->GetHairStyle(); - auto hair_color = target->GetHairColor(); auto helm_texture = target->GetHelmTexture(); - auto heritage = target->GetDrakkinHeritage(); auto race = target->GetModel(); auto size = target->GetSize(); - auto tattoo = target->GetDrakkinTattoo(); auto texture = target->GetTexture(); - std::string feature_changed; - float value_changed; + float value_changed = 0.0f; if (is_beard) { - beard = static_cast(std::stoul(sep->arg[2])); + face.beard = static_cast(std::stoul(sep->arg[2])); feature_changed = "Beard"; - value_changed = beard; + value_changed = face.beard; } else if (is_beard_color) { - beard_color = static_cast(std::stoul(sep->arg[2])); + face.beardcolor = static_cast(std::stoul(sep->arg[2])); feature_changed = "Beard Color"; - value_changed = beard_color; + value_changed = face.beardcolor; } else if (is_details) { if (target->GetRace() != DRAKKIN) { c->Message(Chat::White, "You must target a Drakkin to use this command."); return; } - details = static_cast(std::stoul(sep->arg[2])); + face.drakkin_details = static_cast(std::stoul(sep->arg[2])); feature_changed = "Drakkin Details"; - value_changed = details; + value_changed = static_cast(face.drakkin_details); + } else if (is_eyes) { + face.eyecolor1 = static_cast(std::stoul(sep->arg[2])); + feature_changed = "Eyes"; + value_changed = face.eyecolor1; // eyecolor2 isn't used } else if (is_face) { - face = static_cast(std::stoul(sep->arg[2])); + face.face = static_cast(std::stoul(sep->arg[2])); feature_changed = "Face"; - value_changed = face; + value_changed = face.face; } else if (is_gender) { gender = static_cast(std::stoul(sep->arg[2])); feature_changed = "Gender"; value_changed = gender; } else if (is_hair) { - hair = static_cast(std::stoul(sep->arg[2])); + face.hairstyle = static_cast(std::stoul(sep->arg[2])); feature_changed = "Hair"; - value_changed = hair; + value_changed = face.hairstyle; } else if (is_hair_color) { - hair_color = static_cast(std::stoul(sep->arg[2])); + face.haircolor = static_cast(std::stoul(sep->arg[2])); feature_changed = "Hair Color"; - value_changed = hair_color; + value_changed = face.haircolor; } else if (is_helm) { helm_texture = static_cast(std::stoul(sep->arg[2])); feature_changed = "Helmet Texture"; @@ -154,16 +165,16 @@ void command_feature(Client *c, const Seperator *sep) return; } - heritage = static_cast(std::stoul(sep->arg[2])); + face.drakkin_heritage = static_cast(std::stoul(sep->arg[2])); feature_changed = "Drakkin Heritage"; - value_changed = heritage; + value_changed = static_cast(face.drakkin_heritage); } else if (is_race) { race = static_cast(std::stoul(sep->arg[2])); feature_changed = "Race"; value_changed = race; } else if (is_size) { size = std::stof(sep->arg[2]); - + if (size < 0 || size > 255) { c->Message(Chat::White, "Usage: #feature size [Size] - Change your or your target's Size temporarily (Valid values are 0 to 255, decimal increments are allowed.)"); return; @@ -177,33 +188,43 @@ void command_feature(Client *c, const Seperator *sep) return; } - tattoo = static_cast(std::stoul(sep->arg[2])); + face.drakkin_tattoo = static_cast(std::stoul(sep->arg[2])); feature_changed = "Drakkin Tattoos"; - value_changed = tattoo; + value_changed = static_cast(face.drakkin_tattoo); } else if (is_texture) { texture = static_cast(std::stoul(sep->arg[2])); feature_changed = "Texture"; value_changed = texture; } - target->SendIllusionPacket( - race, - gender, - texture, - helm_texture, - hair_color, - beard_color, - target->GetEyeColor1(), - target->GetEyeColor2(), - hair, - face, - beard, - 0xFF, - heritage, - tattoo, - details, - size - ); + // For now face number is not set through SetFace. This is because the + // client may not update face features after being set to an invalid face + // until a specific valid face number is re-sent (needs more research) + if (!is_gender && !is_helm && !is_race && !is_size && !is_texture && !is_face) + { + target->SetFaceAppearance(face); + } + else + { + target->SendIllusionPacket( + race, + gender, + texture, + helm_texture, + face.haircolor, + face.beardcolor, + target->GetEyeColor1(), + target->GetEyeColor2(), + face.hairstyle, + face.face, + face.beard, + 0xFF, + face.drakkin_heritage, + face.drakkin_tattoo, + face.drakkin_details, + size + ); + } c->Message( Chat::White, diff --git a/zone/mob.cpp b/zone/mob.cpp index 2b505ab65..dc0032256 100644 --- a/zone/mob.cpp +++ b/zone/mob.cpp @@ -2606,6 +2606,27 @@ void Mob::SendIllusionPacket( ); } +void Mob::SetFaceAppearance(const FaceChange_Struct& face, bool skip_sender) +{ + haircolor = face.haircolor; + beardcolor = face.beardcolor; + eyecolor1 = face.eyecolor1; + eyecolor2 = face.eyecolor2; + hairstyle = face.hairstyle; + luclinface = face.face; + beard = face.beard; + drakkin_heritage = face.drakkin_heritage; + drakkin_tattoo = face.drakkin_tattoo; + drakkin_details = face.drakkin_details; + + EQApplicationPacket outapp(OP_SetFace, sizeof(FaceChange_Struct)); + memcpy(outapp.pBuffer, &face, sizeof(FaceChange_Struct)); + auto buf = reinterpret_cast(outapp.pBuffer); + buf->entity_id = GetID(); + + entity_list.QueueClients(this, &outapp, skip_sender); +} + bool Mob::RandomizeFeatures(bool send_illusion, bool set_variables) { if (IsPlayerRace(GetRace())) { diff --git a/zone/mob.h b/zone/mob.h index b3f868131..9ad8c7ab0 100644 --- a/zone/mob.h +++ b/zone/mob.h @@ -834,6 +834,7 @@ public: uint8 in_hairstyle = 0xFF, uint8 in_luclinface = 0xFF, uint8 in_beard = 0xFF, uint8 in_aa_title = 0xFF, uint32 in_drakkin_heritage = 0xFFFFFFFF, uint32 in_drakkin_tattoo = 0xFFFFFFFF, uint32 in_drakkin_details = 0xFFFFFFFF, float in_size = -1.0f, bool send_appearance_effects = true); + void SetFaceAppearance(const FaceChange_Struct& face, bool skip_sender = false); bool RandomizeFeatures(bool send_illusion = true, bool set_variables = true); virtual void Stun(int duration); virtual void UnStun();