diff --git a/acore.json b/acore.json index 352813896..b521641cc 100644 --- a/acore.json +++ b/acore.json @@ -1,5 +1,5 @@ { "name": "azerothcore-wotlk", - "version": "6.0.0-dev.2", + "version": "6.0.0-dev.3", "license": "AGPL3" } diff --git a/data/sql/updates/db_world/2022_03_23_03.sql b/data/sql/updates/db_world/2022_03_23_03.sql new file mode 100644 index 000000000..d7eb02c15 --- /dev/null +++ b/data/sql/updates/db_world/2022_03_23_03.sql @@ -0,0 +1,35 @@ +-- DB update 2022_03_23_02 -> 2022_03_23_03 +DROP PROCEDURE IF EXISTS `updateDb`; +DELIMITER // +CREATE PROCEDURE updateDb () +proc:BEGIN DECLARE OK VARCHAR(100) DEFAULT 'FALSE'; +SELECT COUNT(*) INTO @COLEXISTS +FROM information_schema.COLUMNS +WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = 'version_db_world' AND COLUMN_NAME = '2022_03_23_02'; +IF @COLEXISTS = 0 THEN LEAVE proc; END IF; +START TRANSACTION; +ALTER TABLE version_db_world CHANGE COLUMN 2022_03_23_02 2022_03_23_03 bit; +SELECT sql_rev INTO OK FROM version_db_world WHERE sql_rev = '1647530609689243500'; IF OK <> 'FALSE' THEN LEAVE proc; END IF; +-- +-- START UPDATING QUERIES +-- + +INSERT INTO `version_db_world` (`sql_rev`) VALUES ('1647530609689243500'); + +DELETE FROM `creature` WHERE `guid` = 84205 AND `id1` = 14459; +INSERT INTO `creature` (`guid`, `id1`, `map`, `zoneId`, `areaId`, `spawnMask`, `phaseMask`,`equipment_id`, `position_x`, `position_y`, `position_z`, `orientation`, `spawntimesecs`, `wander_distance`, `currentwaypoint`, `curhealth`, `curmana`, `MovementType`, `npcflag`, `unit_flags`, `dynamicflags`, `ScriptName`, `VerifiedBuild`) VALUES +(84205,14459,469,0,0,1,1,0,-7644.53,-1081.53,408.574,5.2709,10,0,0,42,0,0,0,0,0,'',0); + +DELETE FROM `creature_text` WHERE `CreatureID` = 14459; +INSERT INTO `creature_text` (`CreatureID`, `GroupID`, `ID`, `Text`, `Type`, `Language`, `Probability`, `Emote`, `Duration` ,`Sound`, `BroadcastTextId`, `TextRange`, `comment`) VALUES +(14459, 0, 0, '%s flee as the controlling power of the orb is drained.', 16, 0, 100, 0, 0, 0, 9592, 3, ''); + +-- +-- END UPDATING QUERIES +-- +UPDATE version_db_world SET date = '2022_03_23_03' WHERE sql_rev = '1647530609689243500'; +COMMIT; +END // +DELIMITER ; +CALL updateDb(); +DROP PROCEDURE IF EXISTS `updateDb`; diff --git a/doc/changelog/master.md b/doc/changelog/master.md index 913877108..bc889f228 100644 --- a/doc/changelog/master.md +++ b/doc/changelog/master.md @@ -1,3 +1,15 @@ +## 6.0.0-dev.3 | Commit: [44b7a0666c78dc99ab0bbc94045abb6685b3ad86 +](https://github.com/azerothcore/azerothcore-wotlk/commit/44b7a0666c78dc99ab0bbc94045abb6685b3ad86 + + +### Added + +- New hook for OnQuestComputeXP(). The intended use is to change the XP values for certain quests programmatically. The hook is triggered after XP calculation and before rewarding XP or gold to the player. + +### How to upgrade + +- No special changes needed. The new hook is available for use and should not interfere with any existing hooks or logic. + ## 6.0.0-dev.2 | Commit: [680e60c68b1864596bf23d427e9f4742c6437b86 ](https://github.com/azerothcore/azerothcore-wotlk/commit/680e60c68b1864596bf23d427e9f4742c6437b86 diff --git a/src/server/game/AI/CoreAI/UnitAI.h b/src/server/game/AI/CoreAI/UnitAI.h index 5e7d98f61..af3caec97 100644 --- a/src/server/game/AI/CoreAI/UnitAI.h +++ b/src/server/game/AI/CoreAI/UnitAI.h @@ -334,6 +334,9 @@ public: static AISpellInfoType* AISpellInfo; static void FillAISpellInfo(); + // Called when a summon reaches a waypoint or point movement finished. + virtual void SummonMovementInform(Creature* /*creature*/, uint32 /*motionType*/, uint32 /*point*/) { } + virtual void sGossipHello(Player* /*player*/) {} virtual void sGossipSelect(Player* /*player*/, uint32 /*sender*/, uint32 /*action*/) {} virtual void sGossipSelectCode(Player* /*player*/, uint32 /*sender*/, uint32 /*action*/, char const* /*code*/) {} diff --git a/src/server/game/Entities/Creature/Creature.cpp b/src/server/game/Entities/Creature/Creature.cpp index 201460365..ceeccd9e5 100644 --- a/src/server/game/Entities/Creature/Creature.cpp +++ b/src/server/game/Entities/Creature/Creature.cpp @@ -3529,3 +3529,8 @@ void Creature::SetRespawnTime(uint32 respawn) { m_respawnTime = respawn ? GameTime::GetGameTime().count() + respawn : 0; } + +void Creature::SetCorpseRemoveTime(uint32 delay) +{ + m_corpseRemoveTime = GameTime::GetGameTime().count() + delay; +} diff --git a/src/server/game/Entities/Creature/Creature.h b/src/server/game/Entities/Creature/Creature.h index 2c97cfd54..3fc15806c 100644 --- a/src/server/game/Entities/Creature/Creature.h +++ b/src/server/game/Entities/Creature/Creature.h @@ -69,6 +69,7 @@ public: void GetRespawnPosition(float& x, float& y, float& z, float* ori = nullptr, float* dist = nullptr) const; void SetCorpseDelay(uint32 delay) { m_corpseDelay = delay; } + void SetCorpseRemoveTime(uint32 delay); [[nodiscard]] uint32 GetCorpseDelay() const { return m_corpseDelay; } [[nodiscard]] bool IsRacialLeader() const { return GetCreatureTemplate()->RacialLeader; } [[nodiscard]] bool IsCivilian() const { return GetCreatureTemplate()->flags_extra & CREATURE_FLAG_EXTRA_CIVILIAN; } @@ -394,7 +395,7 @@ protected: ObjectGuid::LowType m_lootRecipientGroup; /// Timers - time_t m_corpseRemoveTime; // (msecs)timer for death or corpse disappearance + time_t m_corpseRemoveTime; // (secs) timer for death or corpse disappearance time_t m_respawnTime; // (secs) time of next respawn time_t m_respawnedTime; // (secs) time when creature respawned uint32 m_respawnDelay; // (secs) delay between corpse disappearance and respawning diff --git a/src/server/game/Instances/InstanceScript.cpp b/src/server/game/Instances/InstanceScript.cpp index 034f52880..8a2dcfced 100644 --- a/src/server/game/Instances/InstanceScript.cpp +++ b/src/server/game/Instances/InstanceScript.cpp @@ -408,6 +408,22 @@ void InstanceScript::DoRespawnGameObject(ObjectGuid uiGuid, uint32 uiTimeToDespa } } +void InstanceScript::DoRespawnCreature(ObjectGuid guid, bool force) +{ + if (Creature* creature = instance->GetCreature(guid)) + { + creature->Respawn(force); + } +} + +void InstanceScript::DoRespawnCreature(uint32 type, bool force) +{ + if (Creature* creature = instance->GetCreature(GetObjectGuid(type))) + { + creature->Respawn(force); + } +} + void InstanceScript::DoUpdateWorldState(uint32 uiStateId, uint32 uiStateData) { Map::PlayerList const& lPlayers = instance->GetPlayers(); diff --git a/src/server/game/Instances/InstanceScript.h b/src/server/game/Instances/InstanceScript.h index 450af2289..f9846f47d 100644 --- a/src/server/game/Instances/InstanceScript.h +++ b/src/server/game/Instances/InstanceScript.h @@ -194,6 +194,12 @@ public: //Respawns a GO having negative spawntimesecs in gameobject-table void DoRespawnGameObject(ObjectGuid guid, uint32 timeToDespawn = MINUTE); + // Respawns a creature. + void DoRespawnCreature(ObjectGuid guid, bool force = false); + + // Respawns a creature from the creature object storage. + void DoRespawnCreature(uint32 type, bool force = false); + //sends world state update to all players in instance void DoUpdateWorldState(uint32 worldstateId, uint32 worldstateValue); diff --git a/src/server/game/Movement/MovementGenerators/PointMovementGenerator.cpp b/src/server/game/Movement/MovementGenerators/PointMovementGenerator.cpp index 83ec1a36d..501558066 100644 --- a/src/server/game/Movement/MovementGenerators/PointMovementGenerator.cpp +++ b/src/server/game/Movement/MovementGenerators/PointMovementGenerator.cpp @@ -197,6 +197,14 @@ template <> void PointMovementGenerator::MovementInform(Creature* unit { if (unit->AI()) unit->AI()->MovementInform(POINT_MOTION_TYPE, id); + + if (Unit* summoner = unit->GetCharmerOrOwner()) + { + if (UnitAI* AI = summoner->GetAI()) + { + AI->SummonMovementInform(unit, POINT_MOTION_TYPE, id); + } + } } template void PointMovementGenerator::DoInitialize(Player*); diff --git a/src/server/game/Movement/MovementGenerators/WaypointMovementGenerator.cpp b/src/server/game/Movement/MovementGenerators/WaypointMovementGenerator.cpp index b669efa62..9a30e38ca 100644 --- a/src/server/game/Movement/MovementGenerators/WaypointMovementGenerator.cpp +++ b/src/server/game/Movement/MovementGenerators/WaypointMovementGenerator.cpp @@ -256,6 +256,14 @@ void WaypointMovementGenerator::MovementInform(Creature* creature) { if (creature->AI()) creature->AI()->MovementInform(WAYPOINT_MOTION_TYPE, i_currentNode); + + if (Unit* owner = creature->GetCharmerOrOwner()) + { + if (UnitAI* AI = owner->GetAI()) + { + AI->SummonMovementInform(creature, WAYPOINT_MOTION_TYPE, i_currentNode); + } + } } //----------------------------------------------------// diff --git a/src/server/game/Spells/SpellInfoCorrections.cpp b/src/server/game/Spells/SpellInfoCorrections.cpp index ea065e9aa..a90200076 100644 --- a/src/server/game/Spells/SpellInfoCorrections.cpp +++ b/src/server/game/Spells/SpellInfoCorrections.cpp @@ -4243,6 +4243,12 @@ void SpellMgr::LoadSpellInfoCorrections() spellInfo->Effects[EFFECT_0].DieSides = 1250; }); + // Explosion - Razorgore + ApplySpellFix({ 20038 }, [](SpellInfo* spellInfo) + { + spellInfo->Effects[EFFECT_0].RadiusEntry = sSpellRadiusStore.LookupEntry(EFFECT_RADIUS_50000_YARDS); + }); + for (uint32 i = 0; i < GetSpellInfoStoreSize(); ++i) { SpellInfo* spellInfo = mSpellInfoMap[i]; diff --git a/src/server/scripts/EasternKingdoms/BlackrockMountain/BlackwingLair/blackwing_lair.h b/src/server/scripts/EasternKingdoms/BlackrockMountain/BlackwingLair/blackwing_lair.h index fe371cba8..a0adcf52b 100644 --- a/src/server/scripts/EasternKingdoms/BlackrockMountain/BlackwingLair/blackwing_lair.h +++ b/src/server/scripts/EasternKingdoms/BlackrockMountain/BlackwingLair/blackwing_lair.h @@ -39,13 +39,18 @@ enum BWLEncounter // Additional Data DATA_LORD_VICTOR_NEFARIUS = 8, + DATA_GRETHOK = 9, + DATA_NEFARIAN_TROOPS = 10, // Doors - DATA_GO_CHROMAGGUS_DOOR = 9 + DATA_GO_CHROMAGGUS_DOOR = 11 }; enum BWLCreatureIds { + NPC_GRETHOK = 12557, + NPC_BLACKWING_GUARDSMAN = 14456, + NPC_NEFARIAN_TROOPS = 14459, NPC_RAZORGORE = 12435, NPC_BLACKWING_DRAGON = 12422, NPC_BLACKWING_TASKMASTER = 12458, diff --git a/src/server/scripts/EasternKingdoms/BlackrockMountain/BlackwingLair/boss_razorgore.cpp b/src/server/scripts/EasternKingdoms/BlackrockMountain/BlackwingLair/boss_razorgore.cpp index 6ae689c7c..398fe167f 100644 --- a/src/server/scripts/EasternKingdoms/BlackrockMountain/BlackwingLair/boss_razorgore.cpp +++ b/src/server/scripts/EasternKingdoms/BlackrockMountain/BlackwingLair/boss_razorgore.cpp @@ -27,6 +27,8 @@ enum Say SAY_EGGS_BROKEN2 = 1, SAY_EGGS_BROKEN3 = 2, SAY_DEATH = 3, + + EMOTE_TROOPS_RETREAT = 0 }; enum Spells @@ -38,7 +40,12 @@ enum Spells SPELL_CLEAVE = 19632, SPELL_WARSTOMP = 24375, SPELL_FIREBALLVOLLEY = 22425, - SPELL_CONFLAGRATION = 23023 + SPELL_CONFLAGRATION = 23023, + + SPELL_EXPLODE_ORB = 20037, + SPELL_EXPLOSION = 20038, // Instakill everything. + + SPELL_WARMING_FLAMES = 23040, }; enum Summons @@ -72,16 +79,19 @@ public: void Reset() override { _Reset(); - + _died = false; _charmerGUID.Clear(); secondPhase = false; + summons.DespawnAll(); instance->SetData(DATA_EGG_EVENT, NOT_STARTED); } void JustDied(Unit* /*killer*/) override { - _JustDied(); - Talk(SAY_DEATH); + if (secondPhase) + { + _JustDied(); + } } bool CanAIAttack(Unit const* target) const override @@ -93,6 +103,11 @@ public: { _EnterCombat(); + events.ScheduleEvent(EVENT_CLEAVE, 15000); + events.ScheduleEvent(EVENT_STOMP, 35000); + events.ScheduleEvent(EVENT_FIREBALL, 7000); + events.ScheduleEvent(EVENT_CONFLAGRATION, 12000); + instance->SetData(DATA_EGG_EVENT, IN_PROGRESS); } @@ -101,12 +116,26 @@ public: secondPhase = true; _charmerGUID.Clear(); me->RemoveAllAuras(); - me->SetHealth(me->GetMaxHealth()); - events.ScheduleEvent(EVENT_CLEAVE, 15000); - events.ScheduleEvent(EVENT_STOMP, 35000); - events.ScheduleEvent(EVENT_FIREBALL, 7000); - events.ScheduleEvent(EVENT_CONFLAGRATION, 12000); + DoCastSelf(SPELL_WARMING_FLAMES, true); + + if (Creature* troops = instance->GetCreature(DATA_NEFARIAN_TROOPS)) + { + troops->AI()->Talk(EMOTE_TROOPS_RETREAT); + } + + for (ObjectGuid const& guid : _summonGUIDS) + { + if (Creature* creature = ObjectAccessor::GetCreature(*me, guid)) + { + if (creature->IsAlive()) + { + creature->CombatStop(true); + creature->SetReactState(REACT_PASSIVE); + creature->GetMotionMaster()->MovePoint(0, Position(-7560.568848f, -1028.553345f, 408.491211f, 0.523858f)); + } + } + } } void SetGUID(ObjectGuid const guid, int32 /*id*/) override @@ -123,6 +152,13 @@ public: charmer->CastSpell(charmer, SPELL_MIND_EXHAUSTION, true); } } + else + { + if (Unit* charmer = ObjectAccessor::GetUnit(*me, _charmerGUID)) + { + me->TauntApply(charmer); + } + } } void DoAction(int32 action) override @@ -138,12 +174,41 @@ public: } } + void JustSummoned(Creature* summon) override + { + _summonGUIDS.push_back(summon->GetGUID()); + summon->SetOwnerGUID(me->GetGUID()); + summons.Summon(summon); + } + + void SummonMovementInform(Creature* summon, uint32 movementType, uint32 /*pathId*/) override + { + if (movementType == POINT_MOTION_TYPE) + { + summon->DespawnOrUnsummon(); + } + } + void DamageTaken(Unit*, uint32& damage, DamageEffectType, SpellSchoolMask) override { - if (!secondPhase && damage >= me->GetHealth()) + if (!secondPhase && damage >= me->GetHealth() && !_died) { - damage = me->GetHealth() - 1; - EnterEvadeMode(); + // This is required because he kills himself with the explosion spell, causing a loop. + _died = true; + + Talk(SAY_DEATH); + DoCastAOE(SPELL_EXPLODE_ORB); + DoCastAOE(SPELL_EXPLOSION); + + // Respawn shorty in case of failure during phase 1. + me->SetCorpseRemoveTime(25); + me->SetRespawnTime(30); + me->SaveRespawnTime(); + + // Might not be required, safe measure. + me->SetLootRecipient(nullptr); + + instance->SetData(DATA_EGG_EVENT, FAIL); } } @@ -152,7 +217,10 @@ public: if (!UpdateVictim()) return; - events.Update(diff); + if (!me->IsCharmed()) + { + events.Update(diff); + } if (me->HasUnitState(UNIT_STATE_CASTING)) return; @@ -176,18 +244,21 @@ public: case EVENT_CONFLAGRATION: DoCastVictim(SPELL_CONFLAGRATION); if (me->GetVictim() && me->GetVictim()->HasAura(SPELL_CONFLAGRATION)) - if (Unit* target = SelectTarget(SelectTargetMethod::Random, 1, 100, true)) + if (Unit* target = SelectTarget(SelectTargetMethod::Random, 1)) me->TauntApply(target); events.ScheduleEvent(EVENT_CONFLAGRATION, 30000); break; } } + DoMeleeAttackIfReady(); } private: bool secondPhase; + bool _died; ObjectGuid _charmerGUID; + GuidVector _summonGUIDS; }; CreatureAI* GetAI(Creature* creature) const override diff --git a/src/server/scripts/EasternKingdoms/BlackrockMountain/BlackwingLair/instance_blackwing_lair.cpp b/src/server/scripts/EasternKingdoms/BlackrockMountain/BlackwingLair/instance_blackwing_lair.cpp index d7dfb96ac..679039671 100644 --- a/src/server/scripts/EasternKingdoms/BlackrockMountain/BlackwingLair/instance_blackwing_lair.cpp +++ b/src/server/scripts/EasternKingdoms/BlackrockMountain/BlackwingLair/instance_blackwing_lair.cpp @@ -41,6 +41,12 @@ DoorData const doorData[] = { 0, 0, DOOR_TYPE_ROOM, BOUNDARY_NONE} // END }; +ObjectData const creatureData[] = +{ + { NPC_GRETHOK, DATA_GRETHOK }, + { NPC_NEFARIAN_TROOPS, DATA_NEFARIAN_TROOPS } +}; + Position const SummonPosition[8] = { {-7661.207520f, -1043.268188f, 407.199554f, 6.280452f}, @@ -67,7 +73,7 @@ public: //SetHeaders(DataHeader); SetBossNumber(EncounterCount); LoadDoorData(doorData); - //LoadObjectData(creatureData, gameObjectData); + LoadObjectData(creatureData, nullptr); } void Initialize() override @@ -98,6 +104,9 @@ public: if (CreatureAI* razorAI = razor->AI()) razorAI->JustSummoned(creature); break; + case NPC_BLACKWING_GUARDSMAN: + guardList.push_back(creature->GetGUID()); + break; case NPC_NEFARIAN: nefarianGUID = creature->GetGUID(); break; @@ -135,11 +144,14 @@ public: { case GO_BLACK_DRAGON_EGG: if (GetBossState(DATA_FIREMAW) == DONE) + { go->SetPhaseMask(2, true); + } else + { EggList.push_back(go->GetGUID()); + } break; - case GO_PORTCULLIS_RAZORGORE: case GO_PORTCULLIS_VAELASTRASZ: case GO_PORTCULLIS_BROODLORD: @@ -233,10 +245,15 @@ public: if (state == DONE) { for (ObjectGuid const& guid : EggList) + { + // Eggs should be destroyed instead + // @todo: after dynamic spawns if (GameObject* egg = instance->GetGameObject(guid)) + { egg->SetPhaseMask(2, true); + } + } } - SetData(DATA_EGG_EVENT, NOT_STARTED); break; case DATA_CHROMAGGUS: if (state == DONE) @@ -270,7 +287,7 @@ public: switch (data) { case IN_PROGRESS: - _events.ScheduleEvent(EVENT_RAZOR_SPAWN, 45000); + _events.ScheduleEvent(EVENT_RAZOR_SPAWN, 45 * IN_MILLISECONDS); EggEvent = data; EggCount = 0; break; @@ -278,13 +295,19 @@ public: _events.CancelEvent(EVENT_RAZOR_SPAWN); EggEvent = data; EggCount = 0; + for (ObjectGuid const& guid : EggList) { - if (GameObject* egg = instance->GetGameObject(guid)) - { - egg->Respawn(); - } + DoRespawnGameObject(guid, 0); } + + DoRespawnCreature(DATA_GRETHOK); + + for (ObjectGuid const& guid : guardList) + { + DoRespawnCreature(guid); + } + break; case SPECIAL: if (++EggCount >= EggList.size()) @@ -336,12 +359,22 @@ public: void OnUnitDeath(Unit* unit) override { - //! HACK, needed because of buggy CreatureAI after charm - if (unit->GetEntry() == NPC_RAZORGORE && GetBossState(DATA_RAZORGORE_THE_UNTAMED) != DONE) - SetBossState(DATA_RAZORGORE_THE_UNTAMED, DONE); - switch (unit->GetEntry()) { + case NPC_RAZORGORE: + //! HACK, needed because of buggy CreatureAI after charm + if (EggEvent == DONE) + { + if (unit->GetEntry() == NPC_RAZORGORE && GetBossState(DATA_RAZORGORE_THE_UNTAMED) != DONE) + { + SetBossState(DATA_RAZORGORE_THE_UNTAMED, DONE); + } + } + else + { + _events.CancelEvent(EVENT_RAZOR_SPAWN); + } + break; case NPC_BLACK_DRAKONID: case NPC_BLUE_DRAKONID: case NPC_BRONZE_DRAKONID: @@ -379,10 +412,18 @@ public: switch (eventId) { case EVENT_RAZOR_SPAWN: - for (uint8 i = urand(2, 5); i > 0; --i) - if (Creature* summon = instance->SummonCreature(Entry[urand(0, 2)], SummonPosition[urand(0, 7)])) - summon->AI()->DoZoneInCombat(); - _events.ScheduleEvent(EVENT_RAZOR_SPAWN, 12000, 17000); + if (EggEvent == IN_PROGRESS) + { + for (uint8 i = urand(2, 5); i > 0; --i) + { + if (Creature* summon = instance->SummonCreature(Entry[urand(0, 2)], SummonPosition[urand(0, 7)])) + { + summon->AI()->DoZoneInCombat(); + } + } + + _events.ScheduleEvent(EVENT_RAZOR_SPAWN, 12000, 17000); + } break; case EVENT_RAZOR_PHASE_TWO: _events.CancelEvent(EVENT_RAZOR_SPAWN); @@ -462,6 +503,7 @@ public: uint8 EggCount; uint32 EggEvent; GuidList EggList; + GuidList guardList; // Nefarian uint32 NefarianLeftTunnel;