diff --git a/src/server/game/Miscellaneous/SharedDefines.h b/src/server/game/Miscellaneous/SharedDefines.h index 850e5517a9..8fd75e7277 100644 --- a/src/server/game/Miscellaneous/SharedDefines.h +++ b/src/server/game/Miscellaneous/SharedDefines.h @@ -4361,6 +4361,16 @@ enum RaidGroupReason RAID_GROUP_ERR_REQUIREMENTS_UNMATCH = 4 // "You do not meet the requirements to enter this instance." }; +enum class SummonRaidMemberValidateReasonCode : int32 +{ + None = 0, + RealmMismatch = 1, + RaidLocked = 2, + MapConditionFailed = 3, + DeadOrGhost = 4, + Offline = 5 +}; + #define MAX_CREATURE_SPELL_DATA_SLOT 4 #endif diff --git a/src/server/game/Server/Packets/PartyPackets.cpp b/src/server/game/Server/Packets/PartyPackets.cpp index c7d5627a03..b281f70020 100644 --- a/src/server/game/Server/Packets/PartyPackets.cpp +++ b/src/server/game/Server/Packets/PartyPackets.cpp @@ -507,3 +507,45 @@ WorldPacket const* WorldPackets::Party::PartyUpdate::Write() return &_worldPacket; } + +WorldPackets::Party::SummonRaidMemberValidateFailed::SummonRaidMemberValidateFailed(ObjectGuid const& memberGuid, SummonRaidMemberValidateReasonCode reasonCode) + : ServerPacket(SMSG_SUMMON_RAID_MEMBER_VALIDATE_FAILED, 4 + sizeof(SummonRaidMemberValidateReason)) +{ + Members.emplace_back(memberGuid, AsUnderlyingType(reasonCode)); +} + +WorldPacket const* WorldPackets::Party::SummonRaidMemberValidateFailed::Write() +{ + _worldPacket.WriteBits(Members.size(), 23); + + for (SummonRaidMemberValidateReason const& reason : Members) + { + _worldPacket.WriteBit(reason.Member[5]); + _worldPacket.WriteBit(reason.Member[3]); + _worldPacket.WriteBit(reason.Member[1]); + _worldPacket.WriteBit(reason.Member[7]); + _worldPacket.WriteBit(reason.Member[2]); + _worldPacket.WriteBit(reason.Member[0]); + _worldPacket.WriteBit(reason.Member[6]); + _worldPacket.WriteBit(reason.Member[4]); + } + + _worldPacket.FlushBits(); + + for (SummonRaidMemberValidateReason const& reason : Members) + { + _worldPacket.WriteByteSeq(reason.Member[4]); + _worldPacket.WriteByteSeq(reason.Member[2]); + _worldPacket.WriteByteSeq(reason.Member[0]); + _worldPacket.WriteByteSeq(reason.Member[6]); + _worldPacket.WriteByteSeq(reason.Member[5]); + + _worldPacket << int32(reason.ReasonCode); + + _worldPacket.WriteByteSeq(reason.Member[7]); + _worldPacket.WriteByteSeq(reason.Member[3]); + _worldPacket.WriteByteSeq(reason.Member[1]); + } + + return &_worldPacket; +} diff --git a/src/server/game/Server/Packets/PartyPackets.h b/src/server/game/Server/Packets/PartyPackets.h index 8a73248965..fd4efd4c7a 100644 --- a/src/server/game/Server/Packets/PartyPackets.h +++ b/src/server/game/Server/Packets/PartyPackets.h @@ -23,6 +23,7 @@ #include "Optional.h" class Player; +enum class SummonRaidMemberValidateReasonCode : int32; namespace WorldPackets { @@ -224,6 +225,26 @@ namespace WorldPackets PartyLFGInfo LfgInfo; PartyDifficultySettings DifficultySettings; }; + + struct SummonRaidMemberValidateReason + { + SummonRaidMemberValidateReason(ObjectGuid const& member, int32 reasonCode) : Member(member), ReasonCode(reasonCode) { } + + ObjectGuid Member; + int32 ReasonCode = 0; + }; + + class SummonRaidMemberValidateFailed final : public ServerPacket + { + public: + SummonRaidMemberValidateFailed() : ServerPacket(SMSG_SUMMON_RAID_MEMBER_VALIDATE_FAILED, 4) { } + + SummonRaidMemberValidateFailed(ObjectGuid const& memberGuid, SummonRaidMemberValidateReasonCode reasonCode); + + WorldPacket const* Write() override; + + std::vector Members; + }; } } diff --git a/src/server/game/Server/Protocol/Opcodes.cpp b/src/server/game/Server/Protocol/Opcodes.cpp index 18ee8b240d..f84147d384 100644 --- a/src/server/game/Server/Protocol/Opcodes.cpp +++ b/src/server/game/Server/Protocol/Opcodes.cpp @@ -1160,7 +1160,7 @@ void OpcodeTable::Initialize() DEFINE_SERVER_OPCODE_HANDLER(SMSG_RAID_INSTANCE_MESSAGE, STATUS_NEVER, CONNECTION_TYPE_REALM); DEFINE_SERVER_OPCODE_HANDLER(SMSG_RAID_MARKERS_CHANGED, STATUS_NEVER, CONNECTION_TYPE_REALM); DEFINE_SERVER_OPCODE_HANDLER(SMSG_RAID_READY_CHECK_THROTTLED_ERROR, STATUS_UNHANDLED, CONNECTION_TYPE_REALM); - DEFINE_SERVER_OPCODE_HANDLER(SMSG_RAID_SUMMON_FAILED, STATUS_UNHANDLED, CONNECTION_TYPE_REALM); + DEFINE_SERVER_OPCODE_HANDLER(SMSG_SUMMON_RAID_MEMBER_VALIDATE_FAILED, STATUS_NEVER, CONNECTION_TYPE_REALM); DEFINE_SERVER_OPCODE_HANDLER(SMSG_GENERATE_RANDOM_CHARACTER_NAME_RESULT, STATUS_NEVER, CONNECTION_TYPE_REALM); DEFINE_SERVER_OPCODE_HANDLER(SMSG_RATED_BG_RATING, STATUS_UNHANDLED, CONNECTION_TYPE_REALM); DEFINE_SERVER_OPCODE_HANDLER(SMSG_RATED_BG_STATS, STATUS_NEVER, CONNECTION_TYPE_REALM); diff --git a/src/server/game/Server/Protocol/Opcodes.h b/src/server/game/Server/Protocol/Opcodes.h index 7939ff0b3d..2124dd80b7 100644 --- a/src/server/game/Server/Protocol/Opcodes.h +++ b/src/server/game/Server/Protocol/Opcodes.h @@ -1118,7 +1118,7 @@ enum OpcodeServer SMSG_RAID_INSTANCE_MESSAGE = 0x6E15, SMSG_RAID_MARKERS_CHANGED = 0x10A1, SMSG_RAID_READY_CHECK_THROTTLED_ERROR = 0x2607, - SMSG_RAID_SUMMON_FAILED = 0x18B6, + SMSG_SUMMON_RAID_MEMBER_VALIDATE_FAILED = 0x18B6, SMSG_GENERATE_RANDOM_CHARACTER_NAME_RESULT = 0x38B1, SMSG_RATED_BG_RATING = 0x15A1, SMSG_RATED_BG_STATS = 0x34A1, diff --git a/src/server/game/Spells/Spell.cpp b/src/server/game/Spells/Spell.cpp index 3a4ff25c3a..4850c67f57 100644 --- a/src/server/game/Spells/Spell.cpp +++ b/src/server/game/Spells/Spell.cpp @@ -1808,26 +1808,6 @@ void Spell::SelectImplicitTrajTargets(SpellEffIndex effIndex, SpellImplicitTarge void Spell::SelectEffectTypeImplicitTargets(uint8 effIndex) { - // special case for SPELL_EFFECT_SUMMON_RAF_FRIEND and SPELL_EFFECT_SUMMON_PLAYER - /// @todo this is a workaround - target shouldn't be stored in target map for those spells - switch (m_spellInfo->Effects[effIndex].Effect) - { - case SPELL_EFFECT_SUMMON_RAF_FRIEND: - case SPELL_EFFECT_SUMMON_PLAYER: - if (m_caster->GetTypeId() == TYPEID_PLAYER && m_caster->GetTarget()) - { - WorldObject* target = ObjectAccessor::FindPlayer(m_caster->GetTarget()); - - CallScriptObjectTargetSelectHandlers(target, SpellEffIndex(effIndex), SpellImplicitTargetInfo()); - - if (target && target->ToPlayer()) - AddUnitTarget(target->ToUnit(), 1 << effIndex, false); - } - return; - default: - break; - } - // select spell implicit targets based on effect type if (!m_spellInfo->Effects[effIndex].GetImplicitTargetType()) return; @@ -6125,35 +6105,37 @@ SpellCastResult Spell::CheckCast(bool strict, uint32* param1 /*= nullptr*/, uint } case SPELL_EFFECT_SUMMON_PLAYER: { - if (m_caster->GetTypeId() != TYPEID_PLAYER) - return SPELL_FAILED_BAD_TARGETS; - if (!m_caster->GetTarget()) - return SPELL_FAILED_BAD_TARGETS; - - Player* target = ObjectAccessor::FindPlayer(m_caster->ToPlayer()->GetTarget()); - if (!target || m_caster->ToPlayer() == target || (!target->IsInSameRaidWith(m_caster->ToPlayer()) && m_spellInfo->Id != 48955)) // refer-a-friend spell - return SPELL_FAILED_BAD_TARGETS; - - if (target->HasSummonPending()) - return SPELL_FAILED_SUMMON_PENDING; - - // check if our map is dungeon - MapEntry const* map = sMapStore.LookupEntry(m_caster->GetMapId()); - if (map->IsDungeon()) + // Single target summoning case + if (m_targets.GetOrigUnitTargetGUID().IsEmpty()) { - uint32 mapId = m_caster->GetMap()->GetId(); - Difficulty difficulty = m_caster->GetMap()->GetDifficulty(); - if (map->IsRaid()) - if (InstancePlayerBind* targetBind = target->GetBoundInstance(mapId, difficulty)) - if (InstancePlayerBind* casterBind = m_caster->ToPlayer()->GetBoundInstance(mapId, difficulty)) - if (targetBind->perm && targetBind->save != casterBind->save) - return SPELL_FAILED_TARGET_LOCKED_TO_RAID_INSTANCE; - - InstanceTemplate const* instance = sObjectMgr->GetInstanceTemplate(mapId); - if (!instance) - return SPELL_FAILED_TARGET_NOT_IN_INSTANCE; - if (!target->Satisfy(sObjectMgr->GetAccessRequirement(mapId, difficulty), mapId)) + if (!m_caster->GetTarget()) return SPELL_FAILED_BAD_TARGETS; + + Player* target = ObjectAccessor::FindPlayer(m_caster->ToPlayer()->GetTarget()); + if (!target || m_caster->ToPlayer() == target || (!target->IsInSameRaidWith(m_caster->ToPlayer()) && m_spellInfo->Id != 48955)) // refer-a-friend spell + return SPELL_FAILED_BAD_TARGETS; + + if (target->HasSummonPending()) + return SPELL_FAILED_SUMMON_PENDING; + + // check if our map is dungeon + MapEntry const* map = sMapStore.LookupEntry(m_caster->GetMapId()); + if (map->IsDungeon()) + { + uint32 mapId = m_caster->GetMap()->GetId(); + Difficulty difficulty = m_caster->GetMap()->GetDifficulty(); + if (map->IsRaid()) + if (InstancePlayerBind* targetBind = target->GetBoundInstance(mapId, difficulty)) + if (InstancePlayerBind* casterBind = m_caster->ToPlayer()->GetBoundInstance(mapId, difficulty)) + if (targetBind->perm && targetBind->save != casterBind->save) + return SPELL_FAILED_TARGET_LOCKED_TO_RAID_INSTANCE; + + InstanceTemplate const* instance = sObjectMgr->GetInstanceTemplate(mapId); + if (!instance) + return SPELL_FAILED_TARGET_NOT_IN_INSTANCE; + if (!target->Satisfy(sObjectMgr->GetAccessRequirement(mapId, difficulty), mapId)) + return SPELL_FAILED_BAD_TARGETS; + } } break; } diff --git a/src/server/game/Spells/SpellEffects.cpp b/src/server/game/Spells/SpellEffects.cpp index 1c14aa1211..c05bbd678a 100644 --- a/src/server/game/Spells/SpellEffects.cpp +++ b/src/server/game/Spells/SpellEffects.cpp @@ -39,12 +39,14 @@ #include "Language.h" #include "Log.h" #include "LootMgr.h" +#include "MapManager.h" #include "MiscPackets.h" #include "MotionMaster.h" #include "ObjectAccessor.h" #include "ObjectMgr.h" #include "Opcodes.h" #include "OutdoorPvPMgr.h" +#include "PartyPackets.h" #include "PathGenerator.h" #include "Pet.h" #include "PhasingHandler.h" @@ -678,18 +680,63 @@ void Spell::EffectTriggerRitualOfSummoning(SpellEffIndex effIndex) if (effectHandleMode != SPELL_EFFECT_HANDLE_HIT) return; - uint32 triggered_spell_id = m_spellInfo->Effects[effIndex].TriggerSpell; - SpellInfo const* spellInfo = sSpellMgr->GetSpellInfo(triggered_spell_id); - + uint32 summonSpellId = m_spellInfo->Effects[effIndex].TriggerSpell; + SpellInfo const* spellInfo = sSpellMgr->GetSpellInfo(summonSpellId); if (!spellInfo) { - TC_LOG_ERROR("spells", "EffectTriggerRitualOfSummoning of spell %u: triggering unknown spell id %i.", m_spellInfo->Id, triggered_spell_id); + TC_LOG_ERROR("spells", "EffectTriggerRitualOfSummoning of spell %u: triggering unknown spell id %i.", m_spellInfo->Id, summonSpellId); return; } finish(); - m_caster->CastSpell(nullptr, spellInfo->Id, false); + if (!m_spellInfo->Effects[effIndex].IsTargetingArea()) + m_caster->CastSpell(nullptr, spellInfo->Id, false); + else if (m_caster->IsPlayer()) + { + Player* player = m_caster->ToPlayer(); + Group* group = player->GetGroup(); + if (!group) + return; + + for (Group::MemberSlot const& memberSlot : group->GetMemberSlots()) + { + if (memberSlot.guid == player->GetGUID()) + continue; + + Player* member = ObjectAccessor::FindPlayer(memberSlot.guid); + if (!member || member->GetSession()->PlayerLogout()) + { + player->SendDirectMessage(WorldPackets::Party::SummonRaidMemberValidateFailed(memberSlot.guid, SummonRaidMemberValidateReasonCode::Offline).Write()); + continue; + } + + if (member->isDead()) + { + player->SendDirectMessage(WorldPackets::Party::SummonRaidMemberValidateFailed(memberSlot.guid, SummonRaidMemberValidateReasonCode::DeadOrGhost).Write()); + continue; + } + + Map::EnterState enterState = sMapMgr->PlayerCannotEnter(player->GetMapId(), member, false); + if (enterState != Map::CAN_ENTER) + { + if (enterState == Map::CANNOT_ENTER_INSTANCE_BIND_MISMATCH) + { + // @todo: this will also cause denied summons for dynamic lockouts. Gotta wait for grumpy Shauren to wrap up his lock rework. + player->SendDirectMessage(WorldPackets::Party::SummonRaidMemberValidateFailed(memberSlot.guid, SummonRaidMemberValidateReasonCode::RaidLocked).Write()); + continue; + } + else + { + player->SendDirectMessage(WorldPackets::Party::SummonRaidMemberValidateFailed(memberSlot.guid, SummonRaidMemberValidateReasonCode::MapConditionFailed).Write()); + continue; + } + } + + // Member passed all checks. Cast summon spell + m_caster->CastSpell(member, spellInfo->Id, false); + } + } } void Spell::EffectJump(SpellEffIndex effIndex) @@ -3635,14 +3682,16 @@ void Spell::EffectStuck(SpellEffIndex /*effIndex*/) void Spell::EffectSummonPlayer(SpellEffIndex /*effIndex*/) { - // workaround - this effect should not use target map - if (effectHandleMode != SPELL_EFFECT_HANDLE_HIT_TARGET) + if (effectHandleMode != SPELL_EFFECT_HANDLE_LAUNCH) return; - if (!unitTarget || unitTarget->GetTypeId() != TYPEID_PLAYER) - return; - - unitTarget->ToPlayer()->SendSummonRequestFrom(m_caster); + if (m_targets.GetOrigUnitTargetGUID().IsEmpty()) + { + if (Player* target = ObjectAccessor::FindPlayer(m_caster->GetTarget())) + target->SendSummonRequestFrom(m_caster); + } + else if (Player* target = ObjectAccessor::FindPlayer(m_targets.GetOrigUnitTargetGUID())) + target->SendSummonRequestFrom(m_caster); } void Spell::EffectActivateObject(SpellEffIndex effIndex) @@ -5543,13 +5592,11 @@ void Spell::EffectBind(SpellEffIndex effIndex) void Spell::EffectSummonRaFFriend(SpellEffIndex effIndex) { - if (effectHandleMode != SPELL_EFFECT_HANDLE_HIT_TARGET) + if (effectHandleMode != SPELL_EFFECT_HANDLE_LAUNCH) return; - if (m_caster->GetTypeId() != TYPEID_PLAYER || !unitTarget || unitTarget->GetTypeId() != TYPEID_PLAYER) - return; - - m_caster->CastSpell(unitTarget, m_spellInfo->Effects[effIndex].TriggerSpell, true); + if (Player* target = ObjectAccessor::FindPlayer(m_caster->GetTarget())) + m_caster->CastSpell(nullptr, m_spellInfo->Effects[effIndex].TriggerSpell); } void Spell::EffectUnlockGuildVaultTab(SpellEffIndex effIndex)