Make Lady Liadrin's Midnight gossip do the summon, movie, and skip.

Fix skyriding
This commit is contained in:
luis
2026-09-23 18:35:47 -03:00
parent 3005443985
commit f088494ba0
6 changed files with 309 additions and 11 deletions
@@ -36101,3 +36101,17 @@ bool Player::IsTankPlayer()
{
return GetRoleForGroup() == ROLE_TANK;
}
void Player::UpdateDynamicFlight(bool apply)
{
if (apply)
{
if (!HasAuraType(SPELL_AURA_ADV_FLYING))
AddAura(SPELL_DYNAMIC_FLIGHT, this);
}
else
{
if (HasAuraType(SPELL_AURA_ADV_FLYING))
RemoveAura(SPELL_DYNAMIC_FLIGHT);
}
}
+7
View File
@@ -1854,6 +1854,12 @@ class TC_GAME_API Player final : public Unit, public GridObject<Player>
void ResetSeasonalQuestStatus(uint16 event_id, time_t eventStartTime);
//WowCommunity
enum DragonRidingSpells
{
SPELL_DYNAMIC_FLIGHT = 406095,
SPELL_DRAGONRIDER_ENERGY = 372771
};
void UpdateWeeklyRewardsPeriod();
static uint32 GetRoleBySpecializationId(uint32 specializationId);
uint32 GetRoleForGroup() const;
@@ -3966,6 +3972,7 @@ class TC_GAME_API Player final : public Unit, public GridObject<Player>
void ForceCompleteQuest(uint32 quest_id);
int32 GetQuestObjectiveProgress(uint32 questId, int8 storageIndex) const;
void PlayConversation(uint32 conversationId);
void UpdateDynamicFlight(bool apply = false);
//WowCommunity
};
+30 -4
View File
@@ -9247,6 +9247,10 @@ void Unit::SetFlightCapabilityID(int32 flightCapabilityId, bool clientUpdate)
SetUpdateFieldValue(m_values.ModifyValue(&Unit::m_unitData).ModifyValue(&UF::UnitData::FlightCapabilityID), flightCapabilityId);
// GlideEventSpeedDivisor scales movement speed when the client evaluates which GlideEvent to play.
// 1.0 is the neutral default; 0.0 would cause divide-by-zero on the client.
SetUpdateFieldValue(m_values.ModifyValue(&Unit::m_unitData).ModifyValue(&UF::UnitData::GlideEventSpeedDivisor), 1.0f);
UpdateAdvFlyingSpeed(ADV_FLYING_AIR_FRICTION, clientUpdate);
UpdateAdvFlyingSpeed(ADV_FLYING_MAX_VEL, clientUpdate);
UpdateAdvFlyingSpeed(ADV_FLYING_LIFT_COEFFICIENT, clientUpdate);
@@ -9262,7 +9266,7 @@ void Unit::SetFlightCapabilityID(int32 flightCapabilityId, bool clientUpdate)
UpdateAdvFlyingSpeed(ADV_FLYING_LAUNCH_SPEED_COEFFICIENT, clientUpdate);
}
void Unit::UpdateAdvFlyingSpeed(AdvFlyingRateTypeSingle speedType, bool clientUpdate)
void Unit::UpdateAdvFlyingSpeed(AdvFlyingRateTypeSingle speedType, bool clientUpdate, bool force /*= false*/)
{
FlightCapabilityEntry const* flightCapabilityEntry = sFlightCapabilityStore.LookupEntry(GetFlightCapabilityID());
if (!flightCapabilityEntry)
@@ -9305,7 +9309,7 @@ void Unit::UpdateAdvFlyingSpeed(AdvFlyingRateTypeSingle speedType, bool clientUp
ApplyPct(newValue, pos);
}
if (m_advFlyingSpeed[speedType] == newValue)
if (!force && m_advFlyingSpeed[speedType] == newValue)
return;
m_advFlyingSpeed[speedType] = newValue;
@@ -9323,7 +9327,7 @@ void Unit::UpdateAdvFlyingSpeed(AdvFlyingRateTypeSingle speedType, bool clientUp
}
}
void Unit::UpdateAdvFlyingSpeed(AdvFlyingRateTypeRange speedType, bool clientUpdate)
void Unit::UpdateAdvFlyingSpeed(AdvFlyingRateTypeRange speedType, bool clientUpdate, bool force /*= false*/)
{
FlightCapabilityEntry const* flightCapabilityEntry = sFlightCapabilityStore.LookupEntry(GetFlightCapabilityID());
if (!flightCapabilityEntry)
@@ -9362,7 +9366,7 @@ void Unit::UpdateAdvFlyingSpeed(AdvFlyingRateTypeRange speedType, bool clientUpd
}
}
if (m_advFlyingSpeed[speedType] == min && m_advFlyingSpeed[speedType + 1] == max)
if (!force && m_advFlyingSpeed[speedType] == min && m_advFlyingSpeed[speedType + 1] == max)
return;
m_advFlyingSpeed[speedType] = min;
@@ -15785,3 +15789,25 @@ void Unit::RestoreDeferredDashGravity()
SetDisableGravity(false);
}
void Unit::SendAdvFlyingSpeedBurst()
{
// The complete FlightCapability parameter burst, in the retail order (sniff 66709: sent on every
// mount-engage, strictly AFTER SMSG_MOVE_SET_CAN_ADV_FLY). The client only accepts/keeps the
// physics params once the adv-fly state is enabled, and the change-suppression in
// UpdateAdvFlyingSpeed would swallow most of it - m_advFlyingSpeed is pre-seeded with
// FlightCapability fallback row 1 - so force all 13.
UpdateAdvFlyingSpeed(ADV_FLYING_AIR_FRICTION, true, true);
UpdateAdvFlyingSpeed(ADV_FLYING_MAX_VEL, true, true);
UpdateAdvFlyingSpeed(ADV_FLYING_LIFT_COEFFICIENT, true, true);
UpdateAdvFlyingSpeed(ADV_FLYING_DOUBLE_JUMP_VEL_MOD, true, true);
UpdateAdvFlyingSpeed(ADV_FLYING_GLIDE_START_MIN_HEIGHT, true, true);
UpdateAdvFlyingSpeed(ADV_FLYING_ADD_IMPULSE_MAX_SPEED, true, true);
UpdateAdvFlyingSpeed(ADV_FLYING_BANKING_RATE, true, true);
UpdateAdvFlyingSpeed(ADV_FLYING_PITCHING_RATE_DOWN, true, true);
UpdateAdvFlyingSpeed(ADV_FLYING_PITCHING_RATE_UP, true, true);
UpdateAdvFlyingSpeed(ADV_FLYING_TURN_VELOCITY_THRESHOLD, true, true);
UpdateAdvFlyingSpeed(ADV_FLYING_SURFACE_FRICTION, true, true);
UpdateAdvFlyingSpeed(ADV_FLYING_OVER_MAX_DECELERATION, true, true);
UpdateAdvFlyingSpeed(ADV_FLYING_LAUNCH_SPEED_COEFFICIENT, true, true);
}
+3 -2
View File
@@ -1752,8 +1752,9 @@ class TC_GAME_API Unit : public WorldObject
float GetAdvFlyingSpeed(AdvFlyingRateTypeSingle speedType) const { return m_advFlyingSpeed[speedType]; }
float GetAdvFlyingSpeedMin(AdvFlyingRateTypeRange speedType) const { return m_advFlyingSpeed[speedType]; }
float GetAdvFlyingSpeedMax(AdvFlyingRateTypeRange speedType) const { return m_advFlyingSpeed[speedType + 1]; }
void UpdateAdvFlyingSpeed(AdvFlyingRateTypeSingle speedType, bool clientUpdate);
void UpdateAdvFlyingSpeed(AdvFlyingRateTypeRange speedType, bool clientUpdate);
void UpdateAdvFlyingSpeed(AdvFlyingRateTypeSingle speedType, bool clientUpdate, bool force = false);
void UpdateAdvFlyingSpeed(AdvFlyingRateTypeRange speedType, bool clientUpdate, bool force = false);
void SendAdvFlyingSpeedBurst();
void FollowerAdded(AbstractFollower* f);
void FollowerRemoved(AbstractFollower* f);
@@ -2917,6 +2917,16 @@ void AuraEffect::HandleAuraMounted(AuraApplication const* aurApp, uint8 mode, bo
target->SetCanAdvFly(true);
target->SetCanDoubleJump(true);
target->SetFlightCapabilityID(1, true);
// The client keeps adv-fly physics params only if they arrive after
// SET_CAN_ADV_FLY - force the complete burst now that it is enabled.
target->SendAdvFlyingSpeedBurst();
// Apply the Dynamic Flight aura - retail receives it from the selected
// mount capability; if a generic capability was chosen instead, drop its
// speed aura so mounted speed comes only from 406095.
if (MountCapabilityEntry const* mountCapability = sMountCapabilityStore.LookupEntry(GetAmountAsInt()))
if (mountCapability->ModSpellAuraID > 0 && uint32(mountCapability->ModSpellAuraID) != SPELL_DYNAMIC_FLIGHT)
target->RemoveAurasDueToSpell(mountCapability->ModSpellAuraID, target->GetGUID());
player->UpdateDynamicFlight(true);
// Cast Vigor aura - required by CasterAuraSpell check for active abilities
if (!player->HasAura(372773))
player->CastSpell(player, 372773, true);
@@ -2953,6 +2963,9 @@ void AuraEffect::HandleAuraMounted(AuraApplication const* aurApp, uint8 mode, bo
target->SetFlightCapabilityID(0, true);
// Remove Vigor aura on dismount
target->RemoveAura(372773);
// Remove the Dynamic Flight aura if it was force-applied on mount
if (Player* player = target->ToPlayer())
player->UpdateDynamicFlight(false);
}
}
@@ -3083,13 +3096,22 @@ void AuraEffect::HandleAuraCanTurnWhileFalling(AuraApplication const* aurApp, ui
void AuraEffect::HandleModAdvFlying(AuraApplication const* aurApp, uint8 mode, bool apply) const
{
if (!(mode & AURA_EFFECT_HANDLE_SEND_FOR_CLIENT_MASK))
if (!(mode & AURA_EFFECT_HANDLE_REAL))
return;
Unit* target = aurApp->GetTarget();
target->SetCanDoubleJump(apply || target->HasAura(SPELL_DH_DOUBLE_JUMP));
target->SetCanFly(apply);
target->SetCanAdvFly(apply);
Player* player = aurApp->GetTarget()->ToPlayer();
if (!player)
return;
player->SetCanDoubleJump(apply || player->HasAura(SPELL_DH_DOUBLE_JUMP));
player->SetCanFly(apply);
player->SetCanAdvFly(apply);
// Retail delivers the full FlightCapability parameter burst right after SET_CAN_ADV_FLY on every
// engage (sniff 66709). Sending it earlier is useless: the client only accepts/keeps the physics
// params once the adv-fly state is enabled.
if (apply)
player->SendAdvFlyingSpeedBurst();
}
void AuraEffect::HandleIgnoreMovementForces(AuraApplication const* aurApp, uint8 mode, bool apply) const
@@ -15,18 +15,200 @@
* with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include "DB2Stores.h"
#include "GossipDef.h"
#include "LootItemType.h"
#include "ObjectMgr.h"
#include "Player.h"
#include "QuestDef.h"
#include "QuestMgr.h"
#include "ScriptMgr.h"
#include "SpellScript.h"
#include "Util.h"
#include "midnight_zone_ai.h"
#include <initializer_list>
#include <string_view>
namespace Scripts::QuelThalas::IsleOfQuelDanasMidnight
{
namespace Quests
{
static constexpr uint32 Midnight = 91281;
static constexpr uint32 VoiceFromTheLight = 88719;
static constexpr uint32 LightLastStand = 86852;
}
namespace Credits
{
static constexpr uint32 ImageOfLiadrin = 241677;
static constexpr uint32 SpeakWithImage = 241691; // [DNT] Kill Credit, objective 1 of 88719
static constexpr uint32 TravelToQuelDanas = 239653; // [DNT] Kill Credit, objective 2 of 88719
}
namespace Spells
{
// Item 239151 force-casts 1227747. Gossip aura 1260860 is the same dummy/root pair.
// Neither effect carries a destination. Map 2858 is "12.0 Quel'Danas Scenario"
// (UiMap 2432). The arrival point is quest 88719's travel coordinate, and Z comes
// from the quest ender POI on that map (11148, -4485, 79).
static constexpr uint32 MapQuelDanasScenario = 2858;
static constexpr float ArrivalX = 11124.7f;
static constexpr float ArrivalY = -4509.5f;
static constexpr float ArrivalZ = 79.0f;
static constexpr float ArrivalO = 0.805f;
// points_of_interest 8566. Area 16333 Sanctum of Light is on continent 0,
// the same map this database uses for Silvermoon.
static constexpr float SanctumX = 8457.0302734375f;
static constexpr float SanctumY = -4610.58984375f;
static constexpr float SanctumZ = 48.25709915161132812f;
}
namespace Movies
{
// Spell 1278217 "[SUN] Play Movie" plays Movie.db2 1048.
static constexpr uint32 Intercession = 1048;
}
namespace Gossip
{
static constexpr int32 SummonToQuelDanas = 133523;
// Campaign.db2 335 "Midnight Expansion Intro Skip" (quest lines 5811 and 6130).
static constexpr uint32 IntroSkipCampaign = 335;
static constexpr uint32 IntroSkipQuests[] =
{
91281, 88719, 86769, 86770, 89271, 86780, 86805, 89012,
86806, 86807, 91274, 86834, 86811, 86848, 86849, 86850, 86852,
86733, 86734, 86735, 86736, 86737
};
}
static bool HasOptionFlag(GossipOptionFlags flags, GossipOptionFlags flag)
{
return (static_cast<int32>(flags) & static_cast<int32>(flag)) != 0;
}
static bool TextHas(std::string_view text, std::initializer_list<std::string_view> needles)
{
for (std::string_view needle : needles)
if (StringContainsStringI(text, needle))
return true;
return false;
}
static void CreditCreature(Player* player, uint32 entry, ObjectGuid guid = ObjectGuid::Empty)
{
player->KilledMonsterCredit(entry, guid);
player->TalkedToCreature(entry, guid);
}
static void OfferQuest(Player* player, uint32 questId, Object* giver)
{
if (player->GetQuestStatus(questId) != QUEST_STATUS_NONE)
return;
Quest const* quest = sObjectMgr->GetQuestTemplate(questId);
if (!quest || !player->CanTakeQuest(quest, false))
return;
player->AddQuestAndCheckCompletion(quest, giver);
}
static void TurnInIfComplete(Player* player, uint32 questId, Object* giver)
{
Quest const* quest = sObjectMgr->GetQuestTemplate(questId);
if (!quest || player->GetQuestStatus(questId) != QUEST_STATUS_COMPLETE)
return;
if (player->CanRewardQuest(quest, false))
player->RewardQuest(quest, LootItemType::Item, 0, giver, true);
}
static void AdvanceSummonQuests(Player* player, Creature* image)
{
Object* giver = image ? static_cast<Object*>(image) : static_cast<Object*>(player);
ObjectGuid imageGuid = image ? image->GetGUID() : ObjectGuid::Empty;
OfferQuest(player, Quests::Midnight, giver);
if (player->GetQuestStatus(Quests::Midnight) == QUEST_STATUS_INCOMPLETE)
CreditCreature(player, Credits::ImageOfLiadrin, imageGuid);
TurnInIfComplete(player, Quests::Midnight, giver);
OfferQuest(player, Quests::VoiceFromTheLight, giver);
if (player->GetQuestStatus(Quests::VoiceFromTheLight) == QUEST_STATUS_INCOMPLETE)
CreditCreature(player, Credits::SpeakWithImage);
}
static bool TeleportIfMapExists(Player* player, uint32 mapId, float x, float y, float z, float orientation)
{
if (!sMapStore.LookupEntry(mapId))
return false;
return player->TeleportTo(mapId, x, y, z, orientation);
}
static void TravelToQuelDanas(Player* player, Creature* image)
{
AdvanceSummonQuests(player, image);
player->PlayerTalkClass->SendCloseGossip();
if (!TeleportIfMapExists(player, Spells::MapQuelDanasScenario, Spells::ArrivalX, Spells::ArrivalY, Spells::ArrivalZ, Spells::ArrivalO))
return;
if (player->GetQuestStatus(Quests::VoiceFromTheLight) == QUEST_STATUS_INCOMPLETE)
CreditCreature(player, Credits::TravelToQuelDanas);
}
static void PlayIntercession(Player* player, GossipMenuItem const* item)
{
player->PlayerTalkClass->SendCloseGossip();
if (item->SpellID)
player->CastSpell(player, uint32(*item->SpellID), true);
if (sMovieStore.LookupEntry(Movies::Intercession))
player->SendMovieStart(Movies::Intercession);
}
static void SkipIntroduction(Player* player)
{
player->PlayerTalkClass->SendCloseGossip();
QuestMgr::SkipCampaignForPlayer(Gossip::IntroSkipCampaign, player);
for (uint32 questId : Gossip::IntroSkipQuests)
{
uint32 single[] = { questId };
player->SkipQuests(single);
}
TeleportIfMapExists(player, 0, Spells::SanctumX, Spells::SanctumY, Spells::SanctumZ, 0.0f);
}
// 1227747 / 1260860 - Light's Summons. Server-side teleport; the spell itself only fades and roots.
class spell_midnight_lights_summons : public SpellScript
{
void HandleTeleport(SpellEffIndex /*effIndex*/)
{
if (Player* player = GetHitPlayer())
TravelToQuelDanas(player, nullptr);
}
void PreventStuckAura(SpellEffIndex /*effIndex*/)
{
PreventHitAura();
}
void Register() override
{
OnEffectHitTarget += SpellEffectFn(spell_midnight_lights_summons::HandleTeleport, EFFECT_0, SPELL_EFFECT_APPLY_AURA);
OnEffectHitTarget += SpellEffectFn(spell_midnight_lights_summons::PreventStuckAura, EFFECT_0, SPELL_EFFECT_APPLY_AURA);
OnEffectHitTarget += SpellEffectFn(spell_midnight_lights_summons::PreventStuckAura, EFFECT_2, SPELL_EFFECT_APPLY_AURA);
}
};
// 241677 - Image of Lady Liadrin (Wowhead: Midnight / A Voice from the Light)
struct npc_image_of_lady_liadrin_midnight : public ScriptedAI
{
@@ -36,6 +218,51 @@ struct npc_image_of_lady_liadrin_midnight : public ScriptedAI
{
return Scripts::QuelThalas::MidnightZones::GossipCreditMidnightObjective(me, player);
}
bool OnGossipSelect(Player* player, uint32 /*menuId*/, uint32 gossipListId) override
{
GossipMenuItem const* item = player->PlayerTalkClass->GetGossipMenu().GetItemByIndex(gossipListId);
if (!item)
return false;
std::string_view text = item->OptionText;
bool const summon = item->GossipOptionID == Gossip::SummonToQuelDanas
|| HasOptionFlag(item->Flags, GossipOptionFlags::QuestLabelPrepend)
|| TextHas(text, { "summon me", "beschw" });
bool const movie = HasOptionFlag(item->Flags, GossipOptionFlags::PlayMovieLabelPrepend)
|| TextHas(text, { "Sunwell", "Sonnenbrunnen" });
bool const skip = TextHas(text, { "Sanctum of Light", "Sanktum", "Skip the Midnight", "Einführung" });
bool const heard = TextHas(text, { "heard this tale", "Geschichte" });
if (summon && !skip)
{
TravelToQuelDanas(player, me);
return true;
}
if (movie)
{
PlayIntercession(player, item);
return true;
}
if (skip)
{
SkipIntroduction(player);
return true;
}
if (heard)
{
if (player->GetQuestStatus(Quests::Midnight) == QUEST_STATUS_INCOMPLETE)
CreditCreature(player, Credits::ImageOfLiadrin, me->GetGUID());
player->PlayerTalkClass->SendCloseGossip();
return true;
}
return false;
}
};
// 241575 - Speak with Liadrin to begin the ritual (Wowhead: Light's Last Stand)
@@ -59,4 +286,5 @@ void AddSC_zone_isle_of_queldanas_midnight()
RegisterCreatureAI(npc_image_of_lady_liadrin_midnight);
RegisterCreatureAI(npc_liadrin_lights_last_stand);
RegisterSpellScript(spell_midnight_lights_summons);
}