diff --git a/src/server/game/Entities/Player/Player.cpp b/src/server/game/Entities/Player/Player.cpp index 6caa047be..89686c1b4 100644 --- a/src/server/game/Entities/Player/Player.cpp +++ b/src/server/game/Entities/Player/Player.cpp @@ -33875,3 +33875,9 @@ void Player::SendCtrOptions() const ctrOptions.ChromieTimeExpansionMask = m_playerData->CtrOptions->ChromieTimeExpansionMask; SendDirectMessage(ctrOptions.Write()); } + +void Player::ShowNeutralPlayerFactionSelectUI() +{ + WorldPackets::Misc::FactionSelectUI packet; + GetSession()->SendPacket(packet.Write()); +} diff --git a/src/server/game/Entities/Player/Player.h b/src/server/game/Entities/Player/Player.h index 1d6a6f303..6c4b50855 100644 --- a/src/server/game/Entities/Player/Player.h +++ b/src/server/game/Entities/Player/Player.h @@ -2140,6 +2140,9 @@ class TC_GAME_API Player final : public Unit, public GridObject void ApplyTraitEntryChanges(int32 editedConfigId, WorldPackets::Traits::TraitConfig const& newConfig, bool applyTraits, bool consumeCurrencies); void RenameTraitConfig(int32 editedConfigId, std::string&& newName); void DeleteTraitConfig(int32 deletedConfigId); + + void ShowNeutralPlayerFactionSelectUI(); + void ApplyTraitConfig(int32 configId, bool apply); void ApplyTraitEntry(int32 traitNodeEntryId, int32 rank, int32 grantedRanks, bool apply); void SetActiveCombatTraitConfigID(int32 traitConfigId) { SetUpdateFieldValue(m_values.ModifyValue(&Player::m_activePlayerData).ModifyValue(&UF::ActivePlayerData::ActiveCombatTraitConfigID), traitConfigId); } diff --git a/src/server/game/Handlers/MiscHandler.cpp b/src/server/game/Handlers/MiscHandler.cpp index 58516f119..34886d34f 100644 --- a/src/server/game/Handlers/MiscHandler.cpp +++ b/src/server/game/Handlers/MiscHandler.cpp @@ -1285,3 +1285,138 @@ void WorldSession::HandleCovenantRenownRequestCatchupState(WorldPackets::MajorFa SendPacket(response.Write()); } + +void WorldSession::HandleSelectFactionOpcode(WorldPackets::Misc::FactionSelect& selectFaction) +{ + if (!_player) + return; + + enum FactionSelection + { + JOIN_HORDE = 0, + JOIN_ALLIANCE = 1, + + SPELL_TRIGGER_FACTION_CHOICE_ALLIANCE = 113244, + SPELL_TRIGGER_FACTION_CHOICE_HORDE = 113245 + }; + + TC_LOG_INFO("entities.player", "HandleSelectFactionOpcode: Player {} (GUID: {}) attempting to select faction: {}", + _player ? _player->GetName() : "", + _player ? _player->GetGUID().ToString() : "", + selectFaction.FactionChoice); + + if (_player->GetRace() != RACE_PANDAREN_NEUTRAL) + { + TC_LOG_WARN("entities.player", "HandleSelectFactionOpcode: Player {} (GUID: {}) is not neutral pandaren (race: {}), rejecting faction selection", + _player ? _player->GetName() : "", + _player ? _player->GetGUID().ToString() : "", + _player ? _player->GetRace() : 0); + + // Send error result to client + WorldPackets::Character::NeutralPlayerFactionSelectResult result; + result.Success = false; + result.NewRaceID = _player ? _player->GetRace() : 0; + _player->GetSession()->SendPacket(result.Write()); + return; + } + + if (selectFaction.FactionChoice > JOIN_ALLIANCE) + { + TC_LOG_WARN("entities.player", "HandleSelectFactionOpcode: Player {} (GUID: {}) sent invalid faction choice: {}", + _player->GetName(), _player->GetGUID().ToString(), selectFaction.FactionChoice); + + // Send error result to client + WorldPackets::Character::NeutralPlayerFactionSelectResult result; + result.Success = false; + result.NewRaceID = _player->GetRace(); + _player->GetSession()->SendPacket(result.Write()); + return; + } + + // Additional validation: check if player already has a faction (shouldn't happen but safety check) + if (_player->GetRace() == RACE_PANDAREN_ALLIANCE || _player->GetRace() == RACE_PANDAREN_HORDE) + { + TC_LOG_WARN("entities.player", "HandleSelectFactionOpcode: Player {} (GUID: {}) already has faction (race: {}), rejecting faction selection", + _player->GetName(), _player->GetGUID().ToString(), _player->GetRace()); + + // Send error result to client + WorldPackets::Character::NeutralPlayerFactionSelectResult result; + result.Success = false; + result.NewRaceID = _player->GetRace(); + _player->GetSession()->SendPacket(result.Write()); + return; + } + + Races newRace = RACE_NONE; + uint32 languageSpell1 = 0; + uint32 languageSpell2 = 0; + uint32 triggerSpell = 0; + + switch (selectFaction.FactionChoice) + { + case JOIN_ALLIANCE: + newRace = RACE_PANDAREN_ALLIANCE; + languageSpell1 = SPELL_LEARN_LANGUAGE_COMMON; + languageSpell2 = SPELL_LEARN_LANGUAGE_PANDAREN_ALLIANCE; + triggerSpell = SPELL_TRIGGER_FACTION_CHOICE_ALLIANCE; + break; + case JOIN_HORDE: + newRace = RACE_PANDAREN_HORDE; + languageSpell1 = SPELL_LEARN_LANGUAGE_ORCISH; + languageSpell2 = SPELL_LEARN_LANGUAGE_PANDAREN_HORDE; + triggerSpell = SPELL_TRIGGER_FACTION_CHOICE_HORDE; + break; + default: + break; + } + + _player->SetRace(newRace); + _player->SetFactionForRace(newRace); + _player->SaveToDB(); + _player->LearnSpell(languageSpell1, false); + _player->LearnSpell(languageSpell2, false); + _player->CastSpell(_player, triggerSpell, true); + + // Force client to refresh all reputation factions or sides + uint32 headerFactionId = (newRace == RACE_PANDAREN_ALLIANCE) ? 469 : 67; + + for (FactionEntry const* fe : sFactionStore) + { + if (!fe->CanHaveReputation()) + continue; + + if (fe->ParentFactionID != headerFactionId) + continue; + + _player->GetReputationMgr().SetVisible(fe); + + int32 base = fe->ReputationBase[0]; + + // Apply the base reputation + _player->GetReputationMgr().SetOneFactionReputation(fe, base, false); + + // Get the updated state + FactionState* fs = const_cast(_player->GetReputationMgr().GetState(fe)); + if (!fs) + continue; + + // Mark for sending + fs->needSend = true; + + // Trigger the system messages + fs->VisualStandingIncrease = base; + + // Send the update + _player->GetReputationMgr().SendState(fs); + } + + const char* sideName = (selectFaction.FactionChoice == 0) ? "Horde" : "Alliance"; + TC_LOG_INFO("entities.player", "HandleSelectFactionOpcode: Player {} (GUID: {}) successfully joined {}", + _player->GetName(), _player->GetGUID().ToString(), sideName); + + // Send success result to client + WorldPackets::Character::NeutralPlayerFactionSelectResult result; + result.Success = true; + result.NewRaceID = newRace; + _player->GetSession()->SendPacket(result.Write()); +} diff --git a/src/server/game/Server/Packets/CharacterPackets.cpp b/src/server/game/Server/Packets/CharacterPackets.cpp index bef1e8ec3..23be1704d 100644 --- a/src/server/game/Server/Packets/CharacterPackets.cpp +++ b/src/server/game/Server/Packets/CharacterPackets.cpp @@ -923,5 +923,14 @@ namespace WorldPackets::Character _worldPacket >> SizedString::Data(group.Name); } } + + WorldPacket const* NeutralPlayerFactionSelectResult::Write() + { + _worldPacket << NewRaceID; + _worldPacket >> Bits<1>(Success); + _worldPacket.FlushBits(); + + return &_worldPacket; + } //WowCommunity } diff --git a/src/server/game/Server/Packets/CharacterPackets.h b/src/server/game/Server/Packets/CharacterPackets.h index fb0338a7a..41e2c2763 100644 --- a/src/server/game/Server/Packets/CharacterPackets.h +++ b/src/server/game/Server/Packets/CharacterPackets.h @@ -933,6 +933,17 @@ namespace WorldPackets std::vector Groups; }; + + class NeutralPlayerFactionSelectResult final : public ServerPacket + { + public: + NeutralPlayerFactionSelectResult() : ServerPacket(SMSG_NEUTRAL_PLAYER_FACTION_SELECT_RESULT, 4 + 1) {} + + WorldPacket const* Write() override; + + uint32 NewRaceID = 0; + bool Success = false; + }; } } diff --git a/src/server/game/Server/Packets/MiscPackets.cpp b/src/server/game/Server/Packets/MiscPackets.cpp index 3fd6aefc7..2a397cc16 100644 --- a/src/server/game/Server/Packets/MiscPackets.cpp +++ b/src/server/game/Server/Packets/MiscPackets.cpp @@ -924,4 +924,9 @@ WorldPacket const* SetCtrOptions::Write() return &_worldPacket; } +void WorldPackets::Misc::FactionSelect::Read() +{ + _worldPacket >> FactionChoice; +} + } diff --git a/src/server/game/Server/Packets/MiscPackets.h b/src/server/game/Server/Packets/MiscPackets.h index 91ed548ae..ea9f816cc 100644 --- a/src/server/game/Server/Packets/MiscPackets.h +++ b/src/server/game/Server/Packets/MiscPackets.h @@ -1149,6 +1149,23 @@ namespace WorldPackets uint32 ChromieTimeExpansionMask = 0; }; + class FactionSelect final : public ClientPacket + { + public: + FactionSelect(WorldPacket&& packet) : ClientPacket(CMSG_NEUTRAL_PLAYER_SELECT_FACTION, std::move(packet)) {} + + void Read() override; + + uint8 FactionChoice = 0; + }; + + class FactionSelectUI final : public ServerPacket + { + public: + FactionSelectUI() : ServerPacket(SMSG_SHOW_NEUTRAL_PLAYER_FACTION_SELECT_UI, 0) {} + + WorldPacket const* Write() override { return &_worldPacket; } + }; } } diff --git a/src/server/game/Server/Protocol/Opcodes.cpp b/src/server/game/Server/Protocol/Opcodes.cpp index 34418451f..b2d4b2bfc 100644 --- a/src/server/game/Server/Protocol/Opcodes.cpp +++ b/src/server/game/Server/Protocol/Opcodes.cpp @@ -857,7 +857,7 @@ void OpcodeTable::InitializeClientOpcodes() DEFINE_HANDLER(CMSG_NEIGHBORHOOD_REMOVE_SECONDARY_OWNER, STATUS_LOGGEDIN, PROCESS_THREADUNSAFE, &WorldSession::HandleNeighborhoodRemoveSecondaryOwner); DEFINE_HANDLER(CMSG_NEIGHBORHOOD_SET_PUBLIC_FLAG, STATUS_LOGGEDIN, PROCESS_THREADUNSAFE, &WorldSession::HandleNeighborhoodSetPublicFlag); DEFINE_HANDLER(CMSG_NEIGHBORHOOD_UPDATE_NAME, STATUS_LOGGEDIN, PROCESS_THREADUNSAFE, &WorldSession::HandleNeighborhoodUpdateName); - DEFINE_HANDLER(CMSG_NEUTRAL_PLAYER_SELECT_FACTION, STATUS_UNHANDLED, PROCESS_INPLACE, &WorldSession::Handle_NULL); + DEFINE_HANDLER(CMSG_NEUTRAL_PLAYER_SELECT_FACTION, STATUS_LOGGEDIN, PROCESS_INPLACE, &WorldSession::HandleSelectFactionOpcode); DEFINE_HANDLER(CMSG_NEXT_CINEMATIC_CAMERA, STATUS_LOGGEDIN, PROCESS_THREADUNSAFE, &WorldSession::HandleNextCinematicCamera); DEFINE_HANDLER(CMSG_NPC_CRAFTING_ORDER_REQUEST, STATUS_UNHANDLED, PROCESS_THREADUNSAFE, &WorldSession::Handle_NULL); DEFINE_HANDLER(CMSG_OBJECT_UPDATE_FAILED, STATUS_LOGGEDIN, PROCESS_INPLACE, &WorldSession::HandleObjectUpdateFailedOpcode); @@ -2132,7 +2132,7 @@ void OpcodeTable::InitializeServerOpcodes() DEFINE_SERVER_OPCODE_HANDLER(SMSG_NEIGHBORHOOD_REMOVE_SECONDARY_OWNER_RESPONSE, STATUS_NEVER, CONNECTION_TYPE_REALM); DEFINE_SERVER_OPCODE_HANDLER(SMSG_NEIGHBORHOOD_ROSTER_RESIDENT_UPDATE, STATUS_NEVER, CONNECTION_TYPE_REALM); DEFINE_SERVER_OPCODE_HANDLER(SMSG_NEIGHBORHOOD_UPDATE_NAME_RESPONSE, STATUS_NEVER, CONNECTION_TYPE_REALM); - DEFINE_SERVER_OPCODE_HANDLER(SMSG_NEUTRAL_PLAYER_FACTION_SELECT_RESULT, STATUS_UNHANDLED, CONNECTION_TYPE_REALM); + DEFINE_SERVER_OPCODE_HANDLER(SMSG_NEUTRAL_PLAYER_FACTION_SELECT_RESULT, STATUS_NEVER, CONNECTION_TYPE_REALM); DEFINE_SERVER_OPCODE_HANDLER(SMSG_NEW_DATA_BUILD, STATUS_UNHANDLED, CONNECTION_TYPE_REALM); DEFINE_SERVER_OPCODE_HANDLER(SMSG_NEW_TAXI_PATH, STATUS_NEVER, CONNECTION_TYPE_REALM); DEFINE_SERVER_OPCODE_HANDLER(SMSG_NEW_WORLD, STATUS_NEVER, CONNECTION_TYPE_REALM); @@ -2426,7 +2426,7 @@ void OpcodeTable::InitializeServerOpcodes() DEFINE_SERVER_OPCODE_HANDLER(SMSG_SHIPMENT_FACTION_UPDATE_RESULT, STATUS_UNHANDLED, CONNECTION_TYPE_INSTANCE); DEFINE_SERVER_OPCODE_HANDLER(SMSG_SHOW_DELVES_COMPANION_CONFIGURATION_UI, STATUS_UNHANDLED, CONNECTION_TYPE_REALM); DEFINE_SERVER_OPCODE_HANDLER(SMSG_SHOW_DELVES_DISPLAY_UI, STATUS_UNHANDLED, CONNECTION_TYPE_REALM); - DEFINE_SERVER_OPCODE_HANDLER(SMSG_SHOW_NEUTRAL_PLAYER_FACTION_SELECT_UI, STATUS_UNHANDLED, CONNECTION_TYPE_REALM); + DEFINE_SERVER_OPCODE_HANDLER(SMSG_SHOW_NEUTRAL_PLAYER_FACTION_SELECT_UI, STATUS_NEVER, CONNECTION_TYPE_REALM); DEFINE_SERVER_OPCODE_HANDLER(SMSG_SHOW_QUEST_COMPLETION_TEXT, STATUS_UNHANDLED, CONNECTION_TYPE_REALM); DEFINE_SERVER_OPCODE_HANDLER(SMSG_SHOW_TAXI_NODES, STATUS_NEVER, CONNECTION_TYPE_REALM); DEFINE_SERVER_OPCODE_HANDLER(SMSG_SHOW_TRADE_SKILL_RESPONSE, STATUS_UNHANDLED, CONNECTION_TYPE_REALM); diff --git a/src/server/game/Server/WorldSession.h b/src/server/game/Server/WorldSession.h index c8778481c..88970976d 100644 --- a/src/server/game/Server/WorldSession.h +++ b/src/server/game/Server/WorldSession.h @@ -743,6 +743,7 @@ namespace WorldPackets class RequestLatestSplashScreen; class QueryCountdownTimer; class SetCurrencyFlags; + class FactionSelect; class RequestCurrencyDataForAccountCharacters; class TransferCurrencyFromAccountCharacter; class GetCharacterCurrencyTransferLog; @@ -2241,6 +2242,7 @@ class TC_GAME_API WorldSession void HandleKeyboundOverride(WorldPackets::Spells::KeyboundOverride& keyboundOverride); void HandleQueryCountdownTimer(WorldPackets::Misc::QueryCountdownTimer& queryCountdownTimer); void HandleSetCurrencyFlags(WorldPackets::Misc::SetCurrencyFlags const& setCurrenctFlags); + void HandleSelectFactionOpcode(WorldPackets::Misc::FactionSelect& selectFaction); // Adventure Journal void HandleAdventureJournalOpenQuest(WorldPackets::AdventureJournal::AdventureJournalOpenQuest& openQuest); diff --git a/src/server/game/Spells/SpellDefines.h b/src/server/game/Spells/SpellDefines.h index e85cd9e81..ac99f9603 100644 --- a/src/server/game/Spells/SpellDefines.h +++ b/src/server/game/Spells/SpellDefines.h @@ -268,6 +268,14 @@ enum SpellFacingFlags SPELL_FACING_FLAG_INFRONT = 0x0001 }; +enum SpellLearnLanguage +{ + SPELL_LEARN_LANGUAGE_COMMON = 668, + SPELL_LEARN_LANGUAGE_ORCISH = 669, + SPELL_LEARN_LANGUAGE_PANDAREN_ALLIANCE = 108130, + SPELL_LEARN_LANGUAGE_PANDAREN_HORDE = 108131 +}; + enum TriggerCastFlags : uint32 { TRIGGERED_NONE = 0x00000000, //!< Not triggered diff --git a/src/server/scripts/Pandaria/zone_the_wandering_isle.cpp b/src/server/scripts/Pandaria/zone_the_wandering_isle.cpp index dc14a3ed9..969d6fdc3 100644 --- a/src/server/scripts/Pandaria/zone_the_wandering_isle.cpp +++ b/src/server/scripts/Pandaria/zone_the_wandering_isle.cpp @@ -1669,6 +1669,22 @@ private: }; } +// 108897 - Pandaren Faction Choice +class spell_pandaren_faction_choice : public SpellScript +{ + void HandleScriptEffect(SpellEffIndex /*effIndex*/) + { + if (Unit* caster = GetCaster()) + if (caster->IsPlayer()) + caster->ToPlayer()->ShowNeutralPlayerFactionSelectUI(); + } + + void Register() override + { + OnEffectHit += SpellEffectFn(spell_pandaren_faction_choice::HandleScriptEffect, EFFECT_0, SPELL_EFFECT_SCRIPT_EFFECT); + } +}; + void AddSC_zone_the_wandering_isle() { using namespace Scripts::Pandaria::TheWanderingIsle; @@ -1708,4 +1724,5 @@ void AddSC_zone_the_wandering_isle() RegisterSpellScript(spell_flying_shadow_kick); RegisterCreatureAI(npc_delivery_cart); + RegisterSpellScript(spell_pandaren_faction_choice); }