fix(Scripts/Karazhan): Implement Tenris Mirkblood. (#22551)

Co-authored-by: amed80 <8395873+amed80@users.noreply.github.com>
This commit is contained in:
Benjamin Jackson
2025-08-27 05:28:18 -04:00
committed by GitHub
parent 0fc05ed4d2
commit cdceb775a0
5 changed files with 466 additions and 2 deletions

View File

@@ -0,0 +1,82 @@
SET @SAY_APPROACH = 0,
@SAY_AGGRO = 1,
@SAY_SUMMON = 2,
@GUID = 12748;
DELETE FROM `areatrigger_scripts` WHERE `entry` IN (5014, 5015);
INSERT INTO `areatrigger_scripts` (`entry`, `ScriptName`) VALUES
(5014, 'at_karazhan_mirkblood_approach'),
(5015, 'at_karazhan_mirkblood_entrance');
UPDATE `creature_template` SET `minlevel` = 73, `maxlevel` = 73, `speed_run` = 1.85714285714, `ScriptName` = 'boss_tenris_mirkblood' WHERE `entry` = 28194;
UPDATE `creature_template` SET `speed_walk` = 0.4, `speed_run` = 0.14285714285, `ScriptName` = 'npc_sanguine_spirit' WHERE `entry` = 28232;
UPDATE `creature_template` SET `unit_flags` = 33554432, `AIName` = 'SmartAI' WHERE `entry` = 28485;
UPDATE `creature_template` SET `unit_flags` = 33555200 WHERE `entry` = 28493;
UPDATE `creature_model_info` SET `BoundingRadius` = 0.200000002980232238, `CombatReach` = 0.400000005960464477 WHERE `DisplayID` = 25296;
UPDATE `creature_model_info` SET `BoundingRadius` = 0.465000003576278686, `CombatReach` = 1.5 WHERE `DisplayID` = 25541;
DELETE FROM `creature_text` WHERE `CreatureID` = 28194;
INSERT INTO `creature_text` (`CreatureID`, `GroupID`, `ID`, `Text`, `Type`, `Language`, `Probability`, `Emote`, `Duration`, `Sound`, `BroadcastTextId`, `TextRange`, `comment`) VALUES
(28194, @SAY_APPROACH, 0, 'I smell... $r. Delicious!', 14, 0, 100, 0, 0, 0, 27780, 0, 'Prince Tenris Mirkblood - SAY_APPROACH'),
(28194, @SAY_AGGRO, 0, 'I shall consume you!', 14, 0, 100, 0, 0, 0, 27781, 0, 'Prince Tenris Mirkblood - SAY_AGGRO'),
(28194, @SAY_SUMMON, 0, 'Drink, mortals! Taste my blood! Taste your death!', 12, 0, 100, 0, 0, 0, 27712, 0, 'Prince Tenris Mirkblood - SAY_SUMMON');
UPDATE `gameobject_template` SET `ScriptName` = 'go_blood_drenched_door' WHERE `entry` = 181032;
DELETE FROM `spell_script_names` WHERE `spell_id` IN (50883, 50925, 51013);
INSERT INTO `spell_script_names` (`spell_id`, `ScriptName`) VALUES
(50883, 'spell_mirkblood_blood_mirror_target_picker'),
(50925, 'spell_mirkblood_dash_gash_return_to_tank_pre_spell'),
(51013, 'spell_mirkblood_exsanguinate');
DELETE FROM `spell_linked_spell` WHERE `spell_trigger` = -50845 AND `spell_effect` = -50844;
INSERT INTO `spell_linked_spell` (`spell_trigger`, `spell_effect`, `type`, `comment`) VALUES
(-50845, -50844, 0, 'Tenris Mirkblood Blood Mirror');
DELETE FROM `creature_template_addon` WHERE `entry` IN (28232, 28485, 28493);
INSERT INTO `creature_template_addon` (`entry`, `path_id`, `mount`, `bytes1`, `bytes2`, `emote`, `visibilityDistanceType`, `auras`) VALUES
(28232, 0, 0, 0, 0, 0, 0, '51282'),
(28485, 0, 0, 0, 0, 0, 0, '30987'),
(28493, 0, 0, 0, 0, 383, 0, '');
DELETE FROM `creature_template_movement` WHERE `CreatureId` IN (28485, 28493);
INSERT INTO `creature_template_movement` (`CreatureId`, `Ground`, `Swim`, `Flight`, `Rooted`, `Chase`, `Random`, `InteractionPauseTimer`) VALUES
(28485, 0, 0, 1, 0, 0, 0, NULL),
(28493, 0, 0, 1, 0, 0, 0, NULL);
DELETE FROM `creature` WHERE `guid` BETWEEN @GUID+0 AND @GUID+9 AND `id1` IN (28485, 28493);
INSERT INTO `creature` (`guid`, `id1`, `id2`, `id3`, `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`, `CreateObject`, `Comment`) VALUES
(@GUID+0, 28485, 0, 0, 532, 0, 0, 1, 1, 0, -11087.619, -1996.4193, 82.59072, 0.453785598278045654, 300, 0, 0, 4050, 0, 0, 0, 0, 0, '', 49345, 2, NULL),
(@GUID+1, 28485, 0, 0, 532, 0, 0, 1, 1, 0, -11104.703, -1973.5052, 82.73294, 0.05235987901687622, 300, 0, 0, 4050, 0, 0, 0, 0, 0, '', 49345, 2, NULL),
(@GUID+2, 28485, 0, 0, 532, 0, 0, 1, 1, 0, -11084.556, -1981.4388, 82.4658, 0.575958669185638427, 300, 0, 0, 4050, 0, 0, 0, 0, 0, '', 49345, 2, NULL),
(@GUID+3, 28485, 0, 0, 532, 0, 0, 1, 1, 0, -11091.643, -1961.8134, 82.77006, 0.104719758033752441, 300, 0, 0, 4050, 0, 0, 0, 0, 0, '', 49345, 2, NULL),
(@GUID+4, 28485, 0, 0, 532, 0, 0, 1, 1, 0, -11097.971, -1982.734, 82.39082, 0.418879032135009765, 300, 0, 0, 4050, 0, 0, 0, 0, 0, '', 49345, 2, NULL),
(@GUID+5, 28493, 0, 0, 532, 0, 0, 1, 1, 0, -11097.721, -1982.62, 77.43985, 5.113814830780029296, 300, 0, 0, 4050, 0, 0, 0, 0, 0, '', 49345, 2, NULL),
(@GUID+6, 28493, 0, 0, 532, 0, 0, 1, 1, 0, -11104.523, -1973.4592, 78.07421, 1.396263360977172851, 300, 0, 0, 4050, 0, 0, 0, 0, 0, '', 49345, 2, NULL),
(@GUID+7, 28493, 0, 0, 532, 0, 0, 1, 1, 0, -11084.696, -1981.4202, 77.87848, 4.904375076293945312, 300, 0, 0, 4050, 0, 0, 0, 0, 0, '', 49345, 2, NULL),
(@GUID+8, 28493, 0, 0, 532, 0, 0, 1, 1, 0, -11091.594, -1962.1276, 78.054115, 2.146754980087280273, 300, 0, 0, 4050, 0, 0, 0, 0, 0, '', 49345, 2, NULL),
(@GUID+9, 28493, 0, 0, 532, 0, 0, 1, 1, 0, -11087.617, -1996.2291, 77.72322, 0.436332315206527709, 300, 0, 0, 4050, 0, 0, 0, 0, 0, '', 49345, 2, NULL);
DELETE FROM `game_event_creature` WHERE `eventEntry` = 120 AND `guid` BETWEEN @GUID+0 AND @GUID+9;
INSERT INTO `game_event_creature` (`eventEntry`, `guid`) VALUES
(120, @GUID+0),
(120, @GUID+1),
(120, @GUID+2),
(120, @GUID+3),
(120, @GUID+4),
(120, @GUID+5),
(120, @GUID+6),
(120, @GUID+7),
(120, @GUID+8),
(120, @GUID+9);
DELETE FROM `smart_scripts` WHERE (`entryorguid` = 28485) AND (`source_type` = 0) AND (`id` IN (0));
INSERT INTO `smart_scripts` (`entryorguid`, `source_type`, `id`, `link`, `event_type`, `event_phase_mask`, `event_chance`, `event_flags`, `event_param1`, `event_param2`, `event_param3`, `event_param4`, `event_param5`, `action_type`, `action_param1`, `action_param2`, `action_param3`, `action_param4`, `action_param5`, `action_param6`, `target_type`, `target_param1`, `target_param2`, `target_param3`, `target_param4`, `target_x`, `target_y`, `target_z`, `target_o`, `comment`) VALUES
(28485, 0, 0, 0, 37, 0, 100, 0, 0, 0, 0, 0, 0, 11, 51773, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 'Blood Vat Bunny - On Initialize - Cast \'Scourge Invasion Blood Vat Bunny\'');
DELETE FROM `conditions` WHERE `SourceTypeOrReferenceId` = 13 AND `SourceEntry` = 51773 AND `ConditionTypeOrReference` = 31 AND `ConditionValue2` = 28485;
INSERT INTO `conditions` (`SourceTypeOrReferenceId`, `SourceGroup`, `SourceEntry`, `SourceId`, `ElseGroup`, `ConditionTypeOrReference`, `ConditionTarget`, `ConditionValue1`, `ConditionValue2`, `ConditionValue3`, `NegativeCondition`, `ErrorType`, `ErrorTextId`, `ScriptName`, `Comment`) VALUES
(13, 1, 51773, 0, 0, 31, 0, 3, 28485, 0, 0, 0, 0, '', 'Target must be unit Blood Vat Bunny');
UPDATE `spell_dbc` SET `Effect_1` = 28, `EffectMiscValue_1` = 28232, `EffectMiscValueB_1` = 64 WHERE `ID` = 50996;

View File

@@ -0,0 +1,375 @@
/*
* This file is part of the AzerothCore Project. See AUTHORS file for Copyright information
*
* This program is free software; you can redistribute it and/or modify it
* under the terms of the GNU Affero General Public License as published by the
* Free Software Foundation; either version 3 of the License, or (at your
* option) any later version.
*
* This program is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for
* more details.
*
* You should have received a copy of the GNU General Public License along
* with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include "karazhan.h"
#include "AreaTriggerScript.h"
#include "CreatureScript.h"
#include "GameObjectAI.h"
#include "GameObjectScript.h"
#include "Player.h"
#include "ScriptedCreature.h"
#include "SpellInfo.h"
#include "SpellScript.h"
#include "SpellScriptLoader.h"
#include "UnitAI.h"
enum Text
{
SAY_APPROACH = 0,
SAY_AGGRO = 1,
SAY_SUMMON = 2
};
enum Spells
{
SPELL_BLOOD_MIRROR0 = 50844,
SPELL_BLOOD_MIRROR1 = 50845,
SPELL_BLOOD_MIRROR_TARGET_PICKER = 50883,
SPELL_BLOOD_MIRROR_TRANSITION_VISUAL = 50910,
SPELL_BLOOD_MIRROR_DAMAGE = 50846,
SPELL_BLOOD_TAP = 51135,
SPELL_BLOOD_SWOOP = 50922,
SPELL_DASH_GASH_PRE_SPELL = 50923,
SPELL_DASH_GASH_RETURN_TO_TANK = 50924,
// SPELL_DASH_GASH_RETURN_TO_TANK_PRE_SPELL = 50925,
// SPELL_DASH_GASH_RETURN_TO_TANK_PRE_SPELL_ROOT = 50932,
SPELL_DESPAWN_SANGUINE_SPIRIT_VISUAL = 51214,
SPELL_DESPAWN_SANGUINE_SPIRITS = 51212,
SPELL_SANGUINE_SPIRIT_AURA = 50993,
SPELL_SANGUINE_SPIRIT_PRE_AURA = 51282,
SPELL_SANGUINE_SPIRIT_PRE_AURA2 = 51283,
SPELL_SUMMON_SANGUINE_SPIRIT0 = 50996,
SPELL_SUMMON_SANGUINE_SPIRIT1 = 50998,
// SPELL_SUMMON_SANGUINE_SPIRIT2 = 51204,
SPELL_SUMMON_SANGUINE_SPIRIT_MISSILE_BURST = 51208,
SPELL_SUMMON_SANGUINE_SPIRIT_SHORT_MISSILE_BURST = 51280,
SPELL_SUMMON_SANGUINE_SPIRIT_ON_KILL = 51205,
SPELL_EXSANGUINATE = 51013,
SPELL_DUMMY_NUKE_RANGE_SELF = 51106,
};
enum Events
{
EVENT_SAY = 1,
EVENT_FLAG = 2
};
struct boss_tenris_mirkblood : public BossAI
{
boss_tenris_mirkblood(Creature* creature) : BossAI(creature, DATA_MIRKBLOOD)
{
scheduler.SetValidator([this]
{
return !me->HasUnitState(UNIT_STATE_CASTING);
});
}
void Reset() override
{
_Reset();
me->SetImmuneToPC(true);
ScheduleHealthCheckEvent(50, [&] {
Talk(SAY_SUMMON);
DoCast(SPELL_SUMMON_SANGUINE_SPIRIT_MISSILE_BURST);
});
ScheduleHealthCheckEvent(45, [&] {
ScheduleTimedEvent(10s, 15s, [&] {
DoCast(SPELL_BLOOD_TAP);
}, 15s, 40s);
});
}
void JustEngagedWith(Unit* /*who*/) override
{
DoZoneInCombat();
ScheduleTimedEvent(1s, 5s, [&] {
// Blood Mirror
DoCast(SPELL_BLOOD_MIRROR_TARGET_PICKER);
}, 20s, 50s);
ScheduleTimedEvent(30s, [&] {
// Blood Swoop
DoCast(SPELL_DASH_GASH_PRE_SPELL);
}, 15s, 40s);
ScheduleTimedEvent(6s, 15s, [&] {
// Sanguine Spirit
DoCast(SPELL_SUMMON_SANGUINE_SPIRIT_SHORT_MISSILE_BURST);
}, 6s, 15s);
}
void KilledUnit(Unit* victim) override
{
if (!victim)
return;
DoCast(victim, SPELL_SUMMON_SANGUINE_SPIRIT_ON_KILL);
}
void DamageTaken(Unit* attacker, uint32& damage, DamageEffectType damageType, SpellSchoolMask damageSchoolMask) override
{
BossAI::DamageTaken(attacker, damage, damageType, damageSchoolMask);
if (!me->HasAura(SPELL_BLOOD_MIRROR0))
return;
if (!_mirrorTarget)
return;
int32 damageTaken = damage;
me->CastCustomSpell(_mirrorTarget, SPELL_BLOOD_MIRROR_DAMAGE, &damageTaken, &damageTaken, &damageTaken, true, nullptr, nullptr, me->GetGUID());
}
void SpellHit(Unit* caster, SpellInfo const* spell) override
{
if (spell->Id == SPELL_BLOOD_MIRROR0 && caster != me)
_mirrorTarget = caster;
}
void EnterEvadeMode(EvadeReason why) override
{
_EnterEvadeMode(why);
me->SetImmuneToPC(false);
}
private:
Unit* _mirrorTarget = nullptr;
};
struct npc_sanguine_spirit : public ScriptedAI
{
npc_sanguine_spirit(Creature* creature) : ScriptedAI(creature) {}
void Reset() override
{
scheduler.CancelAll();
me->ApplySpellImmune(0, IMMUNITY_SCHOOL, SPELL_SCHOOL_MASK_ALL, true);
me->SetReactState(REACT_PASSIVE);
DoCastSelf(SPELL_SANGUINE_SPIRIT_PRE_AURA);
scheduler.Schedule(5s, [this](TaskContext /*context*/)
{
DoCastSelf(SPELL_SANGUINE_SPIRIT_PRE_AURA2);
}).Schedule(3s, [this](TaskContext /*context*/)
{
me->SetReactState(REACT_AGGRESSIVE);
me->SetInCombatWithZone();
DoCastSelf(SPELL_SANGUINE_SPIRIT_AURA);
});
}
void UpdateAI(uint32 diff) override
{
scheduler.Update(diff);
UpdateVictim();
}
};
class spell_mirkblood_blood_mirror_target_picker : public SpellScript
{
PrepareSpellScript(spell_mirkblood_blood_mirror_target_picker)
bool Validate(SpellInfo const* /*spell*/) override
{
return ValidateSpellInfo({ SPELL_BLOOD_MIRROR0, SPELL_BLOOD_MIRROR1, SPELL_BLOOD_MIRROR_TRANSITION_VISUAL });
}
void HandleHit()
{
Unit* caster = GetCaster();
if (!caster->ToCreature())
return;
Unit* target = caster->GetAI()->SelectTarget(SelectTargetMethod::Random, 0, 0.0f, true, false);
if (!target) // Only Blood Mirror the tank if they're the only one around
target = caster->GetVictim();
if (!target)
return;
caster->CastSpell(caster, SPELL_BLOOD_MIRROR_TRANSITION_VISUAL, TRIGGERED_FULL_MASK);
caster->CastSpell(target, SPELL_BLOOD_MIRROR_TRANSITION_VISUAL, TRIGGERED_FULL_MASK);
caster->AddAura(SPELL_BLOOD_MIRROR1, caster); // Should be a cast, but channeled spell results in either Mirkblood or player being unactionable
caster->AddAura(SPELL_BLOOD_MIRROR1, target); // Adding aura manually causes visual to not appear properly, but better than breaking gameplay
target->CastSpell(caster, SPELL_BLOOD_MIRROR0); // Clone player
}
void Register() override
{
OnCast += SpellCastFn(spell_mirkblood_blood_mirror_target_picker::HandleHit);
}
};
class spell_mirkblood_dash_gash_return_to_tank_pre_spell : public SpellScript
{
PrepareSpellScript(spell_mirkblood_dash_gash_return_to_tank_pre_spell)
bool Validate(SpellInfo const* /*spell*/) override
{
return ValidateSpellInfo({ SPELL_DASH_GASH_RETURN_TO_TANK });
}
void HandleCast()
{
if (!GetCaster() || !GetCaster()->GetThreatMgr().GetCurrentVictim())
return;
// Probably wrong, maybe don't charge if would charge the same target?
if (GetCaster()->GetDistance2d(GetCaster()->GetThreatMgr().GetCurrentVictim()) < 5.0f)
return;
GetCaster()->CastSpell(GetCaster()->GetVictim(), SPELL_DASH_GASH_RETURN_TO_TANK);
}
void Register() override
{
OnCast += SpellCastFn(spell_mirkblood_dash_gash_return_to_tank_pre_spell::HandleCast);
}
};
class spell_mirkblood_exsanguinate : public SpellScript
{
PrepareSpellScript(spell_mirkblood_exsanguinate)
void CalculateDamage()
{
if (!GetHitUnit())
return;
SetHitDamage(std::max((GetHitUnit()->GetHealth() * 0.66f), 2000.0f));
}
void Register() override
{
OnHit += SpellHitFn(spell_mirkblood_exsanguinate::CalculateDamage);
}
};
class at_karazhan_mirkblood_approach : public AreaTriggerScript
{
public:
at_karazhan_mirkblood_approach() : AreaTriggerScript("at_karazhan_mirkblood_approach") {}
bool OnTrigger(Player* player, AreaTrigger const* /*trigger*/) override
{
if (InstanceScript* instance = player->GetInstanceScript())
if (instance->GetBossState(DATA_MIRKBLOOD) != DONE)
if (Creature* mirkblood = instance->GetCreature(DATA_MIRKBLOOD))
mirkblood->AI()->Talk(SAY_APPROACH, player);
return false;
}
};
class at_karazhan_mirkblood_entrance : public AreaTriggerScript
{
public:
at_karazhan_mirkblood_entrance() : AreaTriggerScript("at_karazhan_mirkblood_entrance") {}
bool OnTrigger(Player* player, AreaTrigger const* /*trigger*/) override
{
if (InstanceScript* instance = player->GetInstanceScript())
if (instance->GetBossState(DATA_MIRKBLOOD) != DONE)
if (Creature* mirkblood = instance->GetCreature(DATA_MIRKBLOOD))
mirkblood->SetImmuneToPC(false);
return false;
}
};
class go_blood_drenched_door : public GameObjectScript
{
public:
go_blood_drenched_door() : GameObjectScript("go_blood_drenched_door") {}
struct go_blood_drenched_doorAI : public GameObjectAI
{
go_blood_drenched_doorAI(GameObject* go) : GameObjectAI(go) {}
EventMap events;
Creature* mirkblood;
Player* opener;
bool GossipHello(Player* player, bool /*reportUse*/) override
{
events.Reset();
if (InstanceScript* instance = player->GetInstanceScript())
if (instance->GetBossState(DATA_MIRKBLOOD) != DONE)
{
opener = player;
mirkblood = instance->GetCreature(DATA_MIRKBLOOD);
events.ScheduleEvent(EVENT_SAY, 1s);
events.ScheduleEvent(EVENT_FLAG, 5s);
me->SetGameObjectFlag(GO_FLAG_NOT_SELECTABLE);
}
return true;
}
void UpdateAI(uint32 diff) override
{
if (events.Empty())
return;
events.Update(diff);
switch (events.ExecuteEvent())
{
case EVENT_SAY:
if (!mirkblood)
return;
mirkblood->AI()->Talk(SAY_AGGRO, opener);
break;
case EVENT_FLAG:
if (!mirkblood)
return;
mirkblood->SetImmuneToPC(false);
me->Delete();
break;
}
}
};
GameObjectAI* GetAI(GameObject* go) const override
{
return new go_blood_drenched_doorAI(go);
}
};
void AddSC_boss_tenris_mirkblood()
{
RegisterKarazhanCreatureAI(boss_tenris_mirkblood);
RegisterKarazhanCreatureAI(npc_sanguine_spirit);
RegisterSpellScript(spell_mirkblood_blood_mirror_target_picker);
RegisterSpellScript(spell_mirkblood_dash_gash_return_to_tank_pre_spell);
RegisterSpellScript(spell_mirkblood_exsanguinate);
new at_karazhan_mirkblood_approach();
new at_karazhan_mirkblood_entrance();
new go_blood_drenched_door();
}

View File

@@ -47,6 +47,7 @@ ObjectData const creatureData[] =
{ NPC_JULIANNE, DATA_JULIANNE },
{ NPC_NIGHTBANE, DATA_NIGHTBANE },
{ NPC_TERESTIAN_ILLHOOF, DATA_TERESTIAN },
{ NPC_TENRIS_MIRKBLOOD, DATA_MIRKBLOOD },
{ 0, 0 }
};

View File

@@ -73,7 +73,9 @@ enum KZDataTypes
DATA_ROAR = 41,
DATA_STRAWMAN = 42,
DATA_TINHEAD = 43,
DATA_TITO = 44
DATA_TITO = 44,
DATA_MIRKBLOOD = 45
};
enum KZOperaEvents
@@ -135,7 +137,9 @@ enum KZCreatures
// Malchezaar Helpers
NPC_INFERNAL_TARGET = 17644,
NPC_INFERNAL_RELAY = 17645
NPC_INFERNAL_RELAY = 17645,
NPC_TENRIS_MIRKBLOOD = 28194
};

View File

@@ -78,6 +78,7 @@ void AddSC_bosses_opera();
void AddSC_boss_netherspite();
void AddSC_karazhan();
void AddSC_boss_nightbane();
void AddSC_boss_tenris_mirkblood();
void AddSC_boss_felblood_kaelthas(); // Magister's Terrace
void AddSC_boss_selin_fireheart();
void AddSC_boss_vexallus();
@@ -229,6 +230,7 @@ void AddEasternKingdomsScripts()
AddSC_boss_netherspite();
AddSC_karazhan();
AddSC_boss_nightbane();
AddSC_boss_tenris_mirkblood();
AddSC_boss_felblood_kaelthas(); // Magister's Terrace
AddSC_boss_selin_fireheart();
AddSC_boss_vexallus();