Merge branch 'master' into Playerbot

This commit is contained in:
Yunfan Li
2025-01-06 23:04:02 +08:00
21 changed files with 531 additions and 62 deletions

View File

@@ -4862,6 +4862,12 @@ void SpellMgr::LoadSpellInfoCorrections()
spellInfo->RequiresSpellFocus = 0;
});
// Booming Voice
ApplySpellFix({ 40080 }, [](SpellInfo* spellInfo)
{
spellInfo->Effects[EFFECT_0].RealPointsPerLevel = 0;
});
for (uint32 i = 0; i < GetSpellInfoStoreSize(); ++i)
{
SpellInfo* spellInfo = mSpellInfoMap[i];

View File

@@ -2690,7 +2690,7 @@ void World::_UpdateGameTime()
}
/// Shutdown the server
void World::ShutdownServ(uint32 time, uint32 options, uint8 exitcode, const std::string& reason)
void World::ShutdownServ(uint32 time, uint32 options, uint8 exitcode, std::string const& reason)
{
// ignore if server shutdown at next tick
@@ -2699,8 +2699,9 @@ void World::ShutdownServ(uint32 time, uint32 options, uint8 exitcode, const std:
_shutdownMask = options;
_exitCode = exitcode;
_shutdownReason = reason;
LOG_WARN("server", "Time left until shutdown/restart: {}", time);
LOG_DEBUG("server.worldserver", "Server shutdown called with ShutdownMask {}, ExitCode {}, Time {}, Reason {}", ShutdownMask(options), ShutdownExitCode(exitcode), secsToTimeString(time), reason);
///- If the shutdown time is 0, set m_stopEvent (except if shutdown is 'idle' with remaining sessions)
if (time == 0)
@@ -2720,32 +2721,45 @@ void World::ShutdownServ(uint32 time, uint32 options, uint8 exitcode, const std:
sScriptMgr->OnShutdownInitiate(ShutdownExitCode(exitcode), ShutdownMask(options));
}
/// Display a shutdown message to the user(s)
void World::ShutdownMsg(bool show, Player* player, const std::string& reason)
/**
* @brief Displays a shutdown message at specific intervals or immediately if required.
*
* Show the time remaining for a server shutdown/restart with a reason appended if one is provided.
* Messages are displayed at regular intervals such as every
* 12 hours, 1 hour, 5 minutes, 1 minute, 30 seconds, 10 seconds,
* and every second in the last 10 seconds.
*
* @param show Forces the message to be displayed immediately.
* @param player The player who should recieve the message (can be nullptr for global messages).
* @param reason The reason for the shutdown, appended to the message if provided.
*/
void World::ShutdownMsg(bool show, Player* player, std::string const& reason)
{
// not show messages for idle shutdown mode
// Do not show a message for idle shutdown
if (_shutdownMask & SHUTDOWN_MASK_IDLE)
return;
///- Display a message every 12 hours, hours, 5 minutes, minute, 5 seconds and finally seconds
if (show ||
(_shutdownTimer < 5 * MINUTE && (_shutdownTimer % 15) == 0) || // < 5 min; every 15 sec
(_shutdownTimer < 15 * MINUTE && (_shutdownTimer % MINUTE) == 0) || // < 15 min ; every 1 min
(_shutdownTimer < 30 * MINUTE && (_shutdownTimer % (5 * MINUTE)) == 0) || // < 30 min ; every 5 min
(_shutdownTimer < 12 * HOUR && (_shutdownTimer % HOUR) == 0) || // < 12 h ; every 1 h
(_shutdownTimer > 12 * HOUR && (_shutdownTimer % (12 * HOUR)) == 0)) // > 12 h ; every 12 h
bool twelveHours = (_shutdownTimer > 12 * HOUR && (_shutdownTimer % (12 * HOUR)) == 0); // > 12 h ; every 12 h
bool oneHour = (_shutdownTimer < 12 * HOUR && (_shutdownTimer % HOUR) == 0); // < 12 h ; every 1 h
bool fiveMin = (_shutdownTimer < 30 * MINUTE && (_shutdownTimer % (5 * MINUTE)) == 0); // < 30 min ; every 5 min
bool oneMin = (_shutdownTimer < 15 * MINUTE && (_shutdownTimer % MINUTE) == 0); // < 15 min ; every 1 min
bool thirtySec = (_shutdownTimer < 5 * MINUTE && (_shutdownTimer % 30) == 0); // < 5 min; every 30 sec
bool tenSec = (_shutdownTimer < 1 * MINUTE && (_shutdownTimer % 10) == 0); // < 1 min; every 10 sec
bool oneSec = (_shutdownTimer < 10 * SECOND && (_shutdownTimer % 1) == 0); // < 10 sec; every 1 sec
///- Display a message every 12 hours, hour, 5 minutes, minute, 30 seconds, 10 seconds and finally seconds
if (show || twelveHours || oneHour || fiveMin || oneMin || thirtySec || tenSec || oneSec)
{
std::string str = secsToTimeString(_shutdownTimer).append(".");
if (!reason.empty())
{
str += " - " + reason;
}
// Display the reason every 12 hours, hour, 5 minutes, minute. At 60 seconds and at 10 seconds
else if (!_shutdownReason.empty() && (twelveHours || oneHour || fiveMin || oneMin || _shutdownTimer == 60 || _shutdownTimer == 10))
str += " - " + _shutdownReason;
ServerMessageType msgid = (_shutdownMask & SHUTDOWN_MASK_RESTART) ? SERVER_MSG_RESTART_TIME : SERVER_MSG_SHUTDOWN_TIME;
SendServerMessage(msgid, str, player);
LOG_DEBUG("server.worldserver", "Server is {} in {}", (_shutdownMask & SHUTDOWN_MASK_RESTART ? "restart" : "shuttingdown"), str);
LOG_WARN("server.worldserver", "Server {} in {}", (_shutdownMask & SHUTDOWN_MASK_RESTART ? "restarting" : "shutdown"), str);
}
}

View File

@@ -245,9 +245,9 @@ public:
/// Are we in the middle of a shutdown?
[[nodiscard]] bool IsShuttingDown() const override { return _shutdownTimer > 0; }
[[nodiscard]] uint32 GetShutDownTimeLeft() const override { return _shutdownTimer; }
void ShutdownServ(uint32 time, uint32 options, uint8 exitcode, const std::string& reason = std::string()) override;
void ShutdownServ(uint32 time, uint32 options, uint8 exitcode, std::string const& reason = std::string()) override;
void ShutdownCancel() override;
void ShutdownMsg(bool show = false, Player* player = nullptr, const std::string& reason = std::string()) override;
void ShutdownMsg(bool show = false, Player* player = nullptr, std::string const& reason = std::string()) override;
static uint8 GetExitCode() { return _exitCode; }
static void StopNow(uint8 exitcode) { _stopEvent = true; _exitCode = exitcode; }
static bool IsStopped() { return _stopEvent; }
@@ -372,6 +372,7 @@ private:
static uint8 _exitCode;
uint32 _shutdownTimer;
uint32 _shutdownMask;
std::string _shutdownReason;
uint32 _cleaningFlags;

View File

@@ -55,10 +55,9 @@ enum PhaseHalazzi
{
PHASE_NONE = 0,
PHASE_LYNX = 1,
PHASE_SPLIT = 2,
PHASE_HUMAN = 3,
PHASE_MERGE = 4,
PHASE_ENRAGE = 5
PHASE_HUMAN = 2,
PHASE_MERGE = 3,
PHASE_ENRAGE = 4
};
enum Yells
@@ -118,15 +117,15 @@ struct boss_halazzi : public BossAI
{
BossAI::DamageTaken(attacker, damage, damagetype, damageSchoolMask);
if (_phase == PHASE_LYNX || _phase == PHASE_ENRAGE)
if (_phase == PHASE_LYNX)
{
uint32 _healthCheckPercentage = 25 * (3 - _transformCount);
if (!HealthAbovePct(_healthCheckPercentage))
EnterPhase(PHASE_SPLIT);
if (me->HealthBelowPctDamaged(_healthCheckPercentage, damage))
EnterPhase(PHASE_HUMAN);
}
else if (_phase == PHASE_HUMAN)
{
if (!HealthAbovePct(20))
if (me->HealthBelowPctDamaged(20, damage))
EnterPhase(PHASE_MERGE);
}
}
@@ -153,20 +152,12 @@ struct boss_halazzi : public BossAI
void EnterPhase(PhaseHalazzi nextPhase)
{
_phase = nextPhase;
switch (nextPhase)
{
case PHASE_ENRAGE:
SetInvincibility(false);
scheduler.Schedule(12s, GROUP_LYNX, [this](TaskContext context)
{
DoCastSelf(SPELL_SUMMON_TOTEM);
context.Repeat(20s);
});
[[fallthrough]];
case PHASE_LYNX:
{
if (_phase == PHASE_MERGE)
me->ResumeChasingVictim();
summons.DespawnAll();
if (_transformCount)
@@ -188,6 +179,8 @@ struct boss_halazzi : public BossAI
}
}
me->ResumeChasingVictim();
scheduler.CancelGroup(GROUP_MERGE);
scheduler.Schedule(5s, 15s, GROUP_LYNX, [this](TaskContext context)
{
@@ -201,16 +194,15 @@ struct boss_halazzi : public BossAI
});
break;
}
case PHASE_SPLIT:
case PHASE_HUMAN:
Talk(SAY_SPLIT);
DoCastSelf(SPELL_TRANSFIGURE, true);
scheduler.Schedule(3s, GROUP_SPLIT, [this](TaskContext /*context*/)
{
DoCastSelf(SPELL_SUMMON_LYNX, true);
});
nextPhase = PHASE_HUMAN;
[[fallthrough]];
case PHASE_HUMAN:
_phase = PHASE_HUMAN;
scheduler.CancelGroup(GROUP_MERGE);
scheduler.CancelGroup(GROUP_LYNX);
scheduler.Schedule(10s, GROUP_HUMAN, [this](TaskContext context)
@@ -240,24 +232,35 @@ struct boss_halazzi : public BossAI
me->GetMotionMaster()->Clear();
me->GetMotionMaster()->MoveFollow(lynx, 0, 0);
++_transformCount;
scheduler.Schedule(2s, GROUP_MERGE, [this](TaskContext context)
scheduler.Schedule(2s, GROUP_MERGE, [this, lynx](TaskContext context)
{
if (Creature* lynx = instance->GetCreature(DATA_SPIRIT_LYNX))
if (lynx)
{
if (me->IsWithinDistInMap(lynx, 6.0f))
{
if (_transformCount < 3)
EnterPhase(PHASE_LYNX);
else
EnterPhase(PHASE_ENRAGE);
EnterPhase(PHASE_LYNX);
// Enrage phase
if (_transformCount == 3)
{
_phase = PHASE_ENRAGE;
SetInvincibility(false);
scheduler.Schedule(12s, GROUP_LYNX, [this](TaskContext context)
{
DoCastSelf(SPELL_SUMMON_TOTEM);
context.Repeat(20s);
});
}
}
context.Repeat(2s);
else
context.Repeat(2s);
}
});
}
break;
default:
break;
}
_phase = nextPhase;
}
void KilledUnit(Unit* victim) override

View File

@@ -21,6 +21,8 @@
#include "GridNotifiersImpl.h"
#include "PassiveAI.h"
#include "ScriptedCreature.h"
#include "SpellScript.h"
#include "SpellScriptLoader.h"
#include "zulaman.h"
enum Yells
@@ -42,6 +44,7 @@ enum Spells
SPELL_FLAME_BREATH = 43140,
SPELL_FIRE_WALL = 43113,
SPELL_ENRAGE = 44779,
SPELL_SUMMON_PLAYERS_DUMMY = 43096,
SPELL_SUMMON_PLAYERS = 43097,
SPELL_TELE_TO_CENTER = 43098, // coord
SPELL_HATCH_ALL = 43144,
@@ -323,11 +326,9 @@ struct boss_janalai : public BossAI
SpawnBombs();
_isBombing = true;
me->GetMap()->DoForAllPlayers([&](Player* player)
{
if (player->IsAlive())
DoTeleportPlayer(player, janalainPos.GetPositionX() - 5 + rand() % 10, janalainPos.GetPositionY() - 5 + rand() % 10, janalainPos.GetPositionZ(), 0.0f);
});
DoCastSelf(SPELL_TELE_TO_CENTER);
DoCastAOE(SPELL_SUMMON_PLAYERS_DUMMY, true);
//DoCast(Temp, SPELL_SUMMON_PLAYERS, true) // core bug, spell does not work if too far
ThrowBombs();
@@ -400,7 +401,7 @@ struct npc_janalai_hatcher : public ScriptedAI
++_repeatCount;
if (me->FindNearestCreature(NPC_EGG, 100.0f))
context.Repeat(4s);
context.Repeat(5s);
else
{
if (WorldObject* summoner = GetSummoner())
@@ -449,8 +450,39 @@ private:
bool _isHatching;
};
class spell_summon_all_players_dummy: public SpellScript
{
PrepareSpellScript(spell_summon_all_players_dummy);
bool Validate(SpellInfo const* /*spell*/) override
{
return ValidateSpellInfo({ SPELL_SUMMON_PLAYERS });
}
void FilterTargets(std::list<WorldObject*>& targets)
{
Position pos = GetCaster()->GetPosition();
targets.remove_if([&, pos](WorldObject* target) -> bool
{
return target->IsWithinBox(pos, 18.0f, 18.0f, 18.0f);
});
}
void OnHit(SpellEffIndex /*effIndex*/)
{
GetCaster()->CastSpell(GetHitUnit(), SPELL_SUMMON_PLAYERS, true);
}
void Register() override
{
OnObjectAreaTargetSelect += SpellObjectAreaTargetSelectFn(spell_summon_all_players_dummy::FilterTargets, EFFECT_0, TARGET_UNIT_SRC_AREA_ENEMY);
OnEffectHitTarget += SpellEffectFn(spell_summon_all_players_dummy::OnHit, EFFECT_0, SPELL_EFFECT_DUMMY);
}
};
void AddSC_boss_janalai()
{
RegisterZulAmanCreatureAI(boss_janalai);
RegisterZulAmanCreatureAI(npc_janalai_hatcher);
RegisterSpellScript(spell_summon_all_players_dummy);
}

View File

@@ -72,10 +72,11 @@ enum Phases
enum NalorakkGroups
{
GROUP_CHECK_DEAD = 1,
GROUP_MOVE = 2,
GROUP_BERSERK = 3,
GROUP_HUMAN = 4,
GROUP_BEAR = 5
GROUP_CHECK_EVADE = 2,
GROUP_MOVE = 3,
GROUP_BERSERK = 4,
GROUP_HUMAN = 5,
GROUP_BEAR = 6
};
struct boss_nalorakk : public BossAI
@@ -109,7 +110,7 @@ struct boss_nalorakk : public BossAI
void MoveInLineOfSight(Unit* who) override
{
if (who->IsPlayer() && _phase < PHASE_START_COMBAT && _active)
if (who->IsPlayer() && !who->ToPlayer()->IsGameMaster() && _phase < PHASE_START_COMBAT && _active)
{
_active = false;
switch (_phase)
@@ -199,6 +200,25 @@ struct boss_nalorakk : public BossAI
me->SetHomePosition(me->GetPosition());
break;
}
_introScheduler.Schedule(10s, GROUP_CHECK_EVADE, [this](TaskContext context)
{
if (CheckAnyEvadeGroup(_waveList))
{
_introScheduler.CancelGroup(GROUP_CHECK_DEAD);
_introScheduler.Schedule(5s, GROUP_CHECK_EVADE, [this](TaskContext context)
{
for (Creature* member : _waveList)
if (member->isMoving())
{
context.Repeat(1s);
return;
}
_active = true;
});
}
else
context.Repeat(10s);
});
}
BossAI::MoveInLineOfSight(who);
}
@@ -310,7 +330,7 @@ struct boss_nalorakk : public BossAI
BossAI::UpdateAI(diff);
}
bool CheckFullyDeadGroup(std::list<Creature* > groupToCheck)
bool CheckFullyDeadGroup(std::list<Creature*> groupToCheck)
{
for (Creature* member : groupToCheck)
{
@@ -322,6 +342,14 @@ struct boss_nalorakk : public BossAI
return true;
}
bool CheckAnyEvadeGroup(std::list<Creature*> groupToCheck)
{
for (Creature* member : groupToCheck)
if (member->IsAlive() && !member->IsInCombat())
return true;
return false;
}
void JustDied(Unit* killer) override
{
BossAI::JustDied(killer);

View File

@@ -381,6 +381,8 @@ struct npc_zuljin_vortex : public ScriptedAI
me->SetSpeed(MOVE_RUN, 1.0f);
me->SetUnitFlag(UNIT_FLAG_NOT_SELECTABLE);
DoZoneInCombat();
// Start attacking random target
ChangeToNewPlayer();
}
void SpellHit(Unit* caster, SpellInfo const* spell) override
@@ -389,13 +391,22 @@ struct npc_zuljin_vortex : public ScriptedAI
DoCast(caster, SPELL_ZAP_DAMAGE, true);
}
void ChangeToNewPlayer()
{
DoResetThreatList();
if (Unit* target = SelectTarget(SelectTargetMethod::Random, 0, 0.0f, true))
{
me->AddThreat(target, 10000000.0f);
}
}
void UpdateAI(uint32 /*diff*/) override
{
UpdateVictim();
//if the vortex reach the target, it change his target to another player
if (me->IsWithinMeleeRange(me->GetVictim()))
AttackStart(SelectTarget(SelectTargetMethod::Random, 0));
ChangeToNewPlayer();
}
};

View File

@@ -20,6 +20,7 @@
#include "Player.h"
#include "ScriptedCreature.h"
#include "ScriptedGossip.h"
#include "SpellAuraEffects.h"
#include "SpellInfo.h"
#include "SpellScript.h"
#include "SpellScriptLoader.h"
@@ -410,6 +411,13 @@ struct npc_harrison_jones : public ScriptedAI
scheduler.Schedule(1s, [this](TaskContext /*task*/)
{
me->SetStandState(UNIT_STAND_STATE_DEAD);
}).Schedule(2s, [this](TaskContext /*task*/)
{
// Send savages to attack players
std::list<Creature*> creatures;
me->GetCreatureListWithEntryInGrid(creatures, NPC_AMANISHI_SAVAGE, 100.0f);
for (Creature* creature : creatures)
creature->SetInCombatWithZone();
});
_instance->StorePersistentData(DATA_TIMED_RUN, 21);
_instance->DoAction(ACTION_START_TIMED_RUN);
@@ -674,6 +682,166 @@ private:
SummonList _summons;
};
struct WorldTriggerHutPred
{
bool operator()(Creature* trigger) const
{
return trigger->GetOrientation() > 2.7f || (trigger->GetOrientation() < 2.7f && 1270.0f < trigger->GetPositionY() && trigger->GetPositionY() < 1280.0f);
}
};
struct WorldTriggerDrumPred
{
bool operator()(Creature* trigger) const
{
return !WorldTriggerHutPred()(trigger);
}
};
enum AmanishiScout
{
NPC_WORLD_TRIGGER = 22515,
POINT_DRUM = 0,
SAY_AGGRO = 0,
SPELL_ALERT_DRUMS = 42177,
SPELL_MULTI_SHOT = 43205,
SPELL_SHOOT = 16496
};
struct npc_amanishi_scout : public ScriptedAI
{
npc_amanishi_scout(Creature* creature) : ScriptedAI(creature) { }
void Reset() override
{
scheduler.CancelAll();
me->SetCombatMovement(false);
}
void JustEngagedWith(Unit* /*who*/) override
{
me->SetInCombatWithZone();
Talk(SAY_AGGRO);
// Move to Drum
std::list<Creature*> triggers;
GetCreatureListWithEntryInGrid(triggers, me, NPC_WORLD_TRIGGER, 50.0f);
triggers.remove_if(WorldTriggerHutPred());
triggers.sort(Acore::ObjectDistanceOrderPred(me));
if (!triggers.empty())
{
me->ClearTarget();
Creature* closestDrum = triggers.front();
me->GetMotionMaster()->MovePoint(POINT_DRUM, closestDrum->GetPositionX(), closestDrum->GetPositionY(), closestDrum->GetPositionZ());
}
else
ScheduleCombat();
}
void MovementInform(uint32 type, uint32 id) override
{
if (type == POINT_MOTION_TYPE && id == POINT_DRUM)
{
DoCastSelf(SPELL_ALERT_DRUMS);
scheduler.Schedule(5s, [this](TaskContext /*context*/)
{
ScheduleCombat();
});
}
}
void ScheduleCombat()
{
me->SetCombatMovement(true);
if (Unit* victim = me->GetVictim())
me->GetMotionMaster()->MoveChase(victim);
scheduler.Schedule(2s, [this](TaskContext context)
{
DoCastVictim(SPELL_SHOOT);
context.Repeat(4s, 5s);
}).Schedule(6s, [this](TaskContext context)
{
DoCastAOE(SPELL_MULTI_SHOT);
context.Repeat(20s, 24s);
});
}
void UpdateAI(uint32 diff) override
{
scheduler.Update(diff);
if (!UpdateVictim())
return;
DoMeleeAttackIfReady();
}
};
enum SpellAlertDrums
{
SPELL_SUMMON_AMANISHI_SENTRIES = 42179
};
class spell_alert_drums : public AuraScript
{
PrepareAuraScript(spell_alert_drums);
bool Validate(SpellInfo const* /*spellInfo*/) override
{
return ValidateSpellInfo({ SPELL_SUMMON_AMANISHI_SENTRIES });
}
void HandleTriggerSpell(AuraEffect const* aurEff)
{
PreventDefaultAction();
if (aurEff->GetTickNumber() == 1)
GetCaster()->CastSpell(GetCaster(), SPELL_SUMMON_AMANISHI_SENTRIES, true);
}
void Register() override
{
OnEffectPeriodic += AuraEffectPeriodicFn(spell_alert_drums::HandleTriggerSpell, EFFECT_0, SPELL_AURA_PERIODIC_TRIGGER_SPELL);
}
};
enum AmanishiSentries
{
SUMMON_AMANISHI_SENTRIES_1 = 42180,
SUMMON_AMANISHI_SENTRIES_2 = 42181,
SUMMON_AMANISHI_SENTRIES_3 = 42182,
SUMMON_AMANISHI_SENTRIES_4 = 42183,
};
class spell_summon_amanishi_sentries : public SpellScript
{
PrepareSpellScript(spell_summon_amanishi_sentries);
constexpr static uint32 spells[4] = { SUMMON_AMANISHI_SENTRIES_1, SUMMON_AMANISHI_SENTRIES_2, SUMMON_AMANISHI_SENTRIES_3, SUMMON_AMANISHI_SENTRIES_4 };
bool Validate(SpellInfo const* /*spellInfo*/) override
{
return ValidateSpellInfo(spells);
}
void HandleScriptEffect(SpellEffIndex /*effIndex*/)
{
std::list<Creature*> triggers;
GetCreatureListWithEntryInGrid(triggers, GetHitUnit(), NPC_WORLD_TRIGGER, 50.0f);
triggers.remove_if(WorldTriggerDrumPred());
if (triggers.empty())
return;
Creature* trigger = Acore::Containers::SelectRandomContainerElement(triggers);
uint8 index_1 = urand(0, 3);
uint8 index_2 = (index_1 + 1) % 4;
trigger->CastSpell(trigger, spells[index_1], true);
trigger->CastSpell(trigger, spells[index_2], true);
}
void Register() override
{
OnEffectHitTarget += SpellEffectFn(spell_summon_amanishi_sentries::HandleScriptEffect, EFFECT_0, SPELL_EFFECT_SCRIPT_EFFECT);
}
};
void AddSC_zulaman()
{
RegisterZulAmanCreatureAI(npc_forest_frog);
@@ -682,4 +850,7 @@ void AddSC_zulaman()
RegisterSpellScript(spell_ritual_of_power);
RegisterZulAmanCreatureAI(npc_amanishi_lookout);
RegisterZulAmanCreatureAI(npc_amanishi_tempest);
RegisterZulAmanCreatureAI(npc_amanishi_scout);
RegisterSpellScript(spell_alert_drums);
RegisterSpellScript(spell_summon_amanishi_sentries);
}

View File

@@ -54,6 +54,7 @@ enum CreatureIds
NPC_HALAZZI = 23577,
NPC_NALORAKK = 23576,
NPC_SPIRIT_LYNX = 24143,
NPC_AMANISHI_SAVAGE = 23889,
NPC_AMANISHI_WARBRINGER = 23580,
NPC_AMANISHI_TRIBESMAN = 23582,
NPC_AMANISHI_MEDICINE_MAN = 23581,