Merge remote-tracking branch 'azerothcore/master'
# Conflicts: # src/server/game/Entities/Item/Item.cpp # src/server/game/Server/WorldSession.cpp
This commit is contained in:
@@ -139,7 +139,8 @@ foreach(APPLICATION_NAME ${APPLICATIONS_BUILD_LIST})
|
||||
game
|
||||
gsoap
|
||||
readline
|
||||
gperftools)
|
||||
gperftools
|
||||
libsidecar)
|
||||
|
||||
if (UNIX AND NOT NOJEM)
|
||||
set(${APP_PROJECT_NAME}_LINK_FLAGS "-pthread -lncurses ${${APP_PROJECT_NAME}_LINK_FLAGS}")
|
||||
|
||||
@@ -27,6 +27,7 @@
|
||||
#include "AuthSocketMgr.h"
|
||||
#include "Banner.h"
|
||||
#include "Config.h"
|
||||
#include "DBUpdater.h"
|
||||
#include "DatabaseEnv.h"
|
||||
#include "DatabaseLoader.h"
|
||||
#include "GitRevision.h"
|
||||
@@ -142,13 +143,23 @@ int main(int argc, char** argv)
|
||||
|
||||
if (sRealmList->GetRealms().empty())
|
||||
{
|
||||
LOG_ERROR("server.authserver", "No valid realms specified.");
|
||||
LOG_ERROR("server.authserver", "No valid realms specified. Possible reasons:");
|
||||
LOG_ERROR("server.authserver", "- the realmlist table of the auth database is empty");
|
||||
LOG_ERROR("server.authserver", "- every realm has flag 3 (REALM_FLAG_VERSION_MISMATCH | REALM_FLAG_OFFLINE), which the realm list "
|
||||
"query excludes. Reset it with: UPDATE realmlist SET flag = 0 WHERE id = <realm id>;");
|
||||
LOG_ERROR("server.authserver", "- no realm address could be resolved, see the resolver errors logged above");
|
||||
return 1;
|
||||
}
|
||||
|
||||
// Stop auth server if dry run
|
||||
if (sConfigMgr->isDryRun())
|
||||
{
|
||||
if (uint32 failed = DBUpdaterUtil::GetFailedUpdateCount())
|
||||
{
|
||||
LOG_FATAL("server.authserver", "Dry run completed with {} failed database update(s), terminating.", failed);
|
||||
return 1;
|
||||
}
|
||||
|
||||
LOG_INFO("server.authserver", "Dry run completed, terminating.");
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -49,10 +49,12 @@
|
||||
#include "SharedDefines.h"
|
||||
#include "SteadyTimer.h"
|
||||
#include "Systemd.h"
|
||||
#include "TC9Sidecar.h"
|
||||
#include "World.h"
|
||||
#include "WorldSessionMgr.h"
|
||||
#include "WorldSocket.h"
|
||||
#include "WorldSocketMgr.h"
|
||||
#include "libsidecar.h"
|
||||
#include <boost/asio/signal_set.hpp>
|
||||
#include <boost/program_options.hpp>
|
||||
#include <csignal>
|
||||
@@ -365,7 +367,8 @@ int main(int argc, char** argv)
|
||||
sWorldSocketMgr.StopNetwork();
|
||||
|
||||
///- Clean database before leaving
|
||||
ClearOnlineAccounts();
|
||||
if (!sToCloud9Sidecar->ClusterModeEnabled())
|
||||
ClearOnlineAccounts();
|
||||
});
|
||||
|
||||
// Set server online (allow connecting now)
|
||||
@@ -397,11 +400,15 @@ int main(int argc, char** argv)
|
||||
cliThread.reset(new std::thread(CliThread), &ShutdownCLIThread);
|
||||
}
|
||||
|
||||
sToCloud9Sidecar->Init(worldPort, realm.Id.Realm);
|
||||
|
||||
WorldUpdateLoop();
|
||||
|
||||
// Shutdown starts here
|
||||
threadPool.reset();
|
||||
|
||||
sToCloud9Sidecar->Deinit();
|
||||
|
||||
sLog->SetSynchronous();
|
||||
|
||||
sScriptMgr->OnShutdown();
|
||||
@@ -460,8 +467,11 @@ bool StartDB()
|
||||
LOG_INFO("server.loading", "Loading World Information...");
|
||||
LOG_INFO("server.loading", "> RealmID: {}", realm.Id.Realm);
|
||||
|
||||
///- Clean the database before starting
|
||||
ClearOnlineAccounts();
|
||||
///- Clean the database before starting.
|
||||
/// Cluster.Enabled is read from config here because sToCloud9Sidecar->Init()
|
||||
/// has not run yet; ClusterModeEnabled() would still be the default false.
|
||||
if (!sConfigMgr->GetOption<bool>("Cluster.Enabled", false))
|
||||
ClearOnlineAccounts();
|
||||
|
||||
///- Insert version info into DB
|
||||
WorldDatabasePreparedStatement* stmt = WorldDatabase.GetPreparedStatement(WORLD_UPD_VERSION);
|
||||
|
||||
@@ -72,6 +72,8 @@
|
||||
# DEBUG
|
||||
# DYNAMIC RESPAWN SETTINGS
|
||||
#
|
||||
# CLUSTER SETTINGS
|
||||
#
|
||||
###################################################################################################
|
||||
|
||||
###################################################################################################
|
||||
@@ -1838,6 +1840,20 @@ PlayerSave.Stats.MinLevel = 0
|
||||
|
||||
PlayerSave.Stats.SaveOnlyOnLogout = 1
|
||||
|
||||
#
|
||||
# PlayerSave.AdditionalSaves
|
||||
# Description: Bitmask of player data to save to the database a few seconds after
|
||||
# important changes instead of waiting for the next periodic save
|
||||
# (PlayerSaveInterval). Reduces the progress lost on a server crash.
|
||||
# Combine the values to enable multiple triggers.
|
||||
# Values: 1 - Inventory and gold (after looting an item of rare or better quality)
|
||||
# 2 - Quest status (after quest status changes)
|
||||
# 4 - Achievements (after completing an achievement)
|
||||
# Default: 0 - (Disabled)
|
||||
# 7 - (All of the above)
|
||||
|
||||
PlayerSave.AdditionalSaves = 0
|
||||
|
||||
#
|
||||
# CleanCharacterDB
|
||||
# Description: Clean out deprecated achievements, skills, spells and talents from the db.
|
||||
@@ -4895,3 +4911,56 @@ Respawn.ForceCompatibilityMode = 0
|
||||
# GAME SETTINGS END #
|
||||
# #
|
||||
###################################################################################################
|
||||
|
||||
###################################################################################################
|
||||
# #
|
||||
# CLUSTER SETTINGS BEGIN #
|
||||
# #
|
||||
###################################################################################################
|
||||
|
||||
###################################################################################################
|
||||
# CLUSTER SETTINGS
|
||||
#
|
||||
# This feature is experimental and still under development. It enables cluster mode, allowing multiple
|
||||
# worldservers to run for a single realm, distributing the load between them. Alongside worldservers,
|
||||
# additional services are required for proper functionality. If you encounter any issues, please report
|
||||
# them to the ToCloud9 project: https://github.com/walkline/ToCloud9.
|
||||
#
|
||||
# Cluster.Enabled
|
||||
# Description: Enables/disables cluster mode.
|
||||
# SECURITY: in cluster mode the ToCloud9 gateway is the trusted
|
||||
# authentication boundary. This worldserver then skips session-key
|
||||
# digest verification, packet encryption, warden, IP/country locks,
|
||||
# ban and minimum-security-level enforcement, and the
|
||||
# character-ownership check on login. The worldserver port must
|
||||
# only be reachable by the gateway, never directly by players.
|
||||
# Default: 0 - (Disabled)
|
||||
# 1 - (Enabled)
|
||||
|
||||
Cluster.Enabled=0
|
||||
|
||||
#
|
||||
# Cluster.AvailableMaps
|
||||
# Description: List of available maps id on this server.
|
||||
# Examples: "" - (Can handle any map)
|
||||
# "0,1,573"
|
||||
|
||||
Cluster.AvailableMaps=""
|
||||
|
||||
#
|
||||
# Cluster.IsCrossrealm
|
||||
# Description: Enables cross-realm functionality for a cross-realm setup.
|
||||
# When enabled, a connection to a MySQL cross-realm reverse proxy is required.
|
||||
# Default: 0 - (Disabled)
|
||||
# 1 - (Enabled)
|
||||
|
||||
Cluster.IsCrossrealm=0
|
||||
|
||||
#
|
||||
###################################################################################################
|
||||
|
||||
###################################################################################################
|
||||
# #
|
||||
# CLUSTER SETTINGS END #
|
||||
# #
|
||||
###################################################################################################
|
||||
|
||||
@@ -642,6 +642,13 @@ void CharacterDatabaseConnection::DoPrepareStatements()
|
||||
// world_state
|
||||
PrepareStatement(CHAR_SEL_WORLD_STATE, "SELECT Id, Data FROM world_state", CONNECTION_SYNCH);
|
||||
PrepareStatement(CHAR_REP_WORLD_STATE, "REPLACE INTO world_state (Id, Data) VALUES(?, ?)", CONNECTION_ASYNC);
|
||||
|
||||
// CHAR_NO_OP_PROVIDE_REALM_CONTEXT is a no-op query that accepts a single parameter: the realm ID.
|
||||
// This query is used specifically in cross-realm scenarios when the database transaction
|
||||
// lacks sufficient context to determine which realm's database the query should target.
|
||||
// By providing the realm ID explicitly, this ensures that mysql reverse proxy will use
|
||||
// correct realm database for the transaction.
|
||||
PrepareStatement(CHAR_NO_OP_PROVIDE_REALM_CONTEXT, "SELECT ? AS no_op", CONNECTION_ASYNC);
|
||||
}
|
||||
|
||||
CharacterDatabaseConnection::CharacterDatabaseConnection(MySQLConnectionInfo& connInfo) : MySQLConnection(connInfo)
|
||||
|
||||
@@ -546,6 +546,8 @@ enum CharacterDatabaseStatements : uint32
|
||||
CHAR_SEL_WORLD_STATE,
|
||||
CHAR_REP_WORLD_STATE,
|
||||
|
||||
CHAR_NO_OP_PROVIDE_REALM_CONTEXT,
|
||||
|
||||
MAX_CHARACTERDATABASE_STATEMENTS
|
||||
};
|
||||
|
||||
|
||||
@@ -64,6 +64,22 @@ std::string& DBUpdaterUtil::corrected_path()
|
||||
return path;
|
||||
}
|
||||
|
||||
uint32& DBUpdaterUtil::failed_updates()
|
||||
{
|
||||
static uint32 count = 0;
|
||||
return count;
|
||||
}
|
||||
|
||||
void DBUpdaterUtil::MarkUpdateFailed()
|
||||
{
|
||||
++failed_updates();
|
||||
}
|
||||
|
||||
uint32 DBUpdaterUtil::GetFailedUpdateCount()
|
||||
{
|
||||
return failed_updates();
|
||||
}
|
||||
|
||||
// Auth Database
|
||||
template<>
|
||||
std::string DBUpdater<LoginDatabaseConnection>::GetConfigEntry()
|
||||
@@ -582,6 +598,10 @@ void DBUpdater<T>::ApplyFile(DatabaseWorkerPool<T>& pool, std::string const& hos
|
||||
"If you are a developer, please fix your sql query.",
|
||||
path.generic_string(), pool.GetConnectionInfo()->database);
|
||||
|
||||
// Recorded in both modes. A dry run does not throw below, so it keeps attempting the
|
||||
// remaining files and this count is the only thing left to fail the run on.
|
||||
DBUpdaterUtil::MarkUpdateFailed();
|
||||
|
||||
if (!sConfigMgr->isDryRun())
|
||||
{
|
||||
if (uint32 delay = sConfigMgr->GetOption<uint32>("Updates.ExceptionShutdownDelay", 10000))
|
||||
|
||||
@@ -60,8 +60,15 @@ public:
|
||||
|
||||
static bool CheckExecutable();
|
||||
|
||||
// Counts every update file that failed to apply, in any mode. A dry run does not throw
|
||||
// on a bad file, so it keeps going and a single run reports all of them; whoever ends
|
||||
// the run must check this and exit non-zero, otherwise CI goes green on a failed import.
|
||||
static void MarkUpdateFailed();
|
||||
static uint32 GetFailedUpdateCount();
|
||||
|
||||
private:
|
||||
static std::string& corrected_path();
|
||||
static uint32& failed_updates();
|
||||
};
|
||||
|
||||
template <class T>
|
||||
|
||||
@@ -596,7 +596,25 @@ void SmartAI::UpdateAI(uint32 diff)
|
||||
return;
|
||||
|
||||
if (mCanAutoAttack)
|
||||
{
|
||||
UpdateMeleeStance();
|
||||
DoMeleeAttackIfReady();
|
||||
}
|
||||
}
|
||||
|
||||
void SmartAI::UpdateMeleeStance()
|
||||
{
|
||||
// Ranged creatures should not switch to melee stance at distance
|
||||
if (!_currentRangeMode || me->IsCrowdControlled())
|
||||
return;
|
||||
|
||||
Unit* victim = me->GetVictim();
|
||||
if (!victim)
|
||||
return;
|
||||
|
||||
bool const canMelee = me->IsWithinMeleeRange(victim);
|
||||
if (canMelee != me->HasUnitState(UNIT_STATE_MELEE_ATTACKING))
|
||||
me->Attack(victim, canMelee);
|
||||
}
|
||||
|
||||
bool SmartAI::IsEscortInvokerInRange()
|
||||
@@ -741,7 +759,9 @@ void SmartAI::EnterEvadeMode(EvadeReason why)
|
||||
|
||||
if (Unit* owner = me->GetCharmerOrOwner())
|
||||
{
|
||||
me->GetMotionMaster()->MoveFollow(owner, PET_FOLLOW_DIST, me->GetFollowAngle());
|
||||
if (!me->IsVehicle()) // vehicles should not follow their owner (passenger)
|
||||
me->GetMotionMaster()->MoveFollow(owner, PET_FOLLOW_DIST, me->GetFollowAngle());
|
||||
|
||||
me->ClearUnitState(UNIT_STATE_EVADE);
|
||||
}
|
||||
else if (HasEscortState(SMART_ESCORT_ESCORTING))
|
||||
@@ -927,7 +947,7 @@ void SmartAI::AttackStart(Unit* who)
|
||||
return;
|
||||
}
|
||||
|
||||
if (who && me->Attack(who, mCanAutoAttack))
|
||||
if (who && me->Attack(who, mCanAutoAttack && !_currentRangeMode))
|
||||
{
|
||||
if (!me->HasUnitState(UNIT_STATE_NO_COMBAT_MOVEMENT))
|
||||
{
|
||||
@@ -1202,7 +1222,12 @@ void SmartAI::SetCurrentRangeMode(bool on, float range)
|
||||
_attackDistance = range;
|
||||
|
||||
if (Unit* victim = me->GetVictim())
|
||||
{
|
||||
me->GetMotionMaster()->MoveChase(victim, _attackDistance);
|
||||
|
||||
if (!on && mCanAutoAttack && !me->HasUnitState(UNIT_STATE_MELEE_ATTACKING))
|
||||
me->Attack(victim, true);
|
||||
}
|
||||
}
|
||||
|
||||
void SmartAI::SetMainSpell(uint32 spellId)
|
||||
|
||||
@@ -265,6 +265,7 @@ private:
|
||||
uint32 mDespawnState;
|
||||
void UpdateDespawn(const uint32 diff);
|
||||
void UpdateFollow(const uint32 diff);
|
||||
void UpdateMeleeStance();
|
||||
uint32 mEscortInvokerCheckTimer;
|
||||
bool mJustReset;
|
||||
|
||||
|
||||
@@ -1775,7 +1775,7 @@ void AchievementMgr::UpdateAchievementCriteria(AchievementCriteriaTypes type, ui
|
||||
{
|
||||
// those requirements couldn't be found in the dbc
|
||||
AchievementCriteriaDataSet const* data = sAchievementMgr->GetCriteriaDataSet(achievementCriteria);
|
||||
if (!data || !data->Meets(GetPlayer(), nullptr))
|
||||
if (!data || !data->Meets(GetPlayer(), unit))
|
||||
continue;
|
||||
|
||||
// Check map id requirement
|
||||
@@ -2333,6 +2333,8 @@ void AchievementMgr::CompletedAchievement(AchievementEntry const* achievement)
|
||||
}
|
||||
}
|
||||
|
||||
_player->AdditionalSavingAddMask(ADDITIONAL_SAVING_ACHIEVEMENTS);
|
||||
|
||||
if (achievement->flags & (ACHIEVEMENT_FLAG_REALM_FIRST_REACH | ACHIEVEMENT_FLAG_REALM_FIRST_KILL) && !_player->GetSession()->HasPermission(rbac::RBAC_PERM_CANNOT_EARN_REALM_FIRST_ACHIEVEMENTS))
|
||||
sAchievementMgr->SetRealmCompleted(achievement);
|
||||
|
||||
|
||||
@@ -342,7 +342,7 @@ void Arena::EndBattleground(TeamId winnerTeamId)
|
||||
// update achievement BEFORE personal rating update
|
||||
uint32 rating = player->GetArenaPersonalRating(winnerArenaTeam->GetSlot());
|
||||
player->UpdateAchievementCriteria(ACHIEVEMENT_CRITERIA_TYPE_WIN_RATED_ARENA, rating ? rating : 1);
|
||||
player->UpdateAchievementCriteria(ACHIEVEMENT_CRITERIA_TYPE_WIN_ARENA, GetMapId());
|
||||
player->UpdateAchievementCriteria(ACHIEVEMENT_CRITERIA_TYPE_WIN_ARENA, GetMapId(), 0, player);
|
||||
|
||||
// Last standing - Rated 5v5 arena & be solely alive player
|
||||
if (GetArenaType() == ARENA_TYPE_5v5 && aliveWinners == 1 && player->IsAlive())
|
||||
|
||||
@@ -35,10 +35,12 @@
|
||||
#include "ObjectMgr.h"
|
||||
#include "Pet.h"
|
||||
#include "Player.h"
|
||||
#include "Realm.h"
|
||||
#include "RBAC.h"
|
||||
#include "ReputationMgr.h"
|
||||
#include "ScriptMgr.h"
|
||||
#include "SpellAuras.h"
|
||||
#include "TC9Sidecar.h"
|
||||
#include "Transport.h"
|
||||
#include "Util.h"
|
||||
#include "World.h"
|
||||
@@ -277,6 +279,12 @@ void Battleground::Update(uint32 diff)
|
||||
if (!GetInvitedCount(TEAM_HORDE) && !GetInvitedCount(TEAM_ALLIANCE))
|
||||
{
|
||||
m_SetDeleteThis = true;
|
||||
|
||||
// Only needed for the sidecar notify inside SetStatus; queue and
|
||||
// spectator code read the status within this manager pass, so do
|
||||
// not change it on non-cluster servers.
|
||||
if (sToCloud9Sidecar->ClusterModeEnabled())
|
||||
SetStatus(STATUS_WAIT_LEAVE);
|
||||
}
|
||||
|
||||
return;
|
||||
@@ -1071,6 +1079,11 @@ void Battleground::RemovePlayerAtLeave(Player* player)
|
||||
// if the player was a match participant
|
||||
if (participant)
|
||||
{
|
||||
if (sToCloud9Sidecar->ClusterModeEnabled())
|
||||
sToCloud9Sidecar->OnPlayerLeftBattleground(player->GetGUID().GetCounter(),
|
||||
player->GetGUID().GetRealmID(),
|
||||
GetInstanceID());
|
||||
|
||||
player->ClearAfkReports();
|
||||
|
||||
WorldPacket data;
|
||||
@@ -1889,3 +1902,11 @@ uint8 Battleground::GetUniqueBracketId() const
|
||||
{
|
||||
return GetMaxLevel() / 10;
|
||||
}
|
||||
|
||||
void Battleground::SetStatus(BattlegroundStatus Status)
|
||||
{
|
||||
m_Status = Status;
|
||||
|
||||
if (sToCloud9Sidecar->ClusterModeEnabled() && GetInstanceID() != 0)
|
||||
sToCloud9Sidecar->OnBattlegroundStatusChanged(GetInstanceID(), Status);
|
||||
}
|
||||
|
||||
@@ -367,7 +367,7 @@ public:
|
||||
void SetRandomTypeID(BattlegroundTypeId TypeID) { m_RandomTypeID = TypeID; }
|
||||
void SetBracket(PvPDifficultyEntry const* bracketEntry);
|
||||
void SetInstanceID(uint32 InstanceID) { m_InstanceID = InstanceID; }
|
||||
void SetStatus(BattlegroundStatus Status) { m_Status = Status; }
|
||||
void SetStatus(BattlegroundStatus Status);
|
||||
void SetClientInstanceID(uint32 InstanceID) { m_ClientInstanceID = InstanceID; }
|
||||
void SetStartTime(uint32 Time) { m_StartTime = Time; }
|
||||
void SetEndTime(uint32 Time) { m_EndTime = Time; }
|
||||
|
||||
@@ -54,7 +54,8 @@ target_link_libraries(game
|
||||
PRIVATE
|
||||
acore-core-interface
|
||||
PUBLIC
|
||||
game-interface)
|
||||
game-interface
|
||||
libsidecar)
|
||||
|
||||
set_target_properties(game
|
||||
PROPERTIES
|
||||
|
||||
@@ -1771,6 +1771,9 @@ bool Creature::LoadCreatureFromDB(ObjectGuid::LowType spawnId, Map* map, bool ad
|
||||
|
||||
SetHealth(m_deathState == DeathState::Alive ? curhealth : 0);
|
||||
|
||||
// SelectLevel() sized the player damage requirement against full health, before curhealth was known
|
||||
ResetPlayerDamageReq();
|
||||
|
||||
// checked at creature_template loading
|
||||
m_defaultMovementType = MovementGeneratorType(data->movementType);
|
||||
|
||||
@@ -2172,14 +2175,17 @@ void Creature::ForcedDespawn(Milliseconds timeMSToDespawn, Seconds forceRespawnT
|
||||
if (forceRespawnTimer > 0s)
|
||||
m_respawnDelay = forceRespawnTimer.count();
|
||||
|
||||
if (IsAlive())
|
||||
bool const wasAlive = IsAlive();
|
||||
|
||||
if (wasAlive)
|
||||
setDeathState(DeathState::JustDied, true);
|
||||
|
||||
// Xinef: Set new respawn time, ignore corpse decay time...
|
||||
// After setDeathState, m_respawnTime includes m_corpseDelay which we don't
|
||||
// want for a forced respawn. Override it so RemoveCorpse's max() picks ours.
|
||||
if (forceRespawnTimer > 0s)
|
||||
m_respawnTime = GameTime::GetGameTime().count() + forceRespawnTimer.count();
|
||||
// setDeathState(JustDied) folds m_corpseDelay into m_respawnTime, but a creature
|
||||
// despawned while alive never leaves a corpse, so the decay must not be charged.
|
||||
// Recompute so RemoveCorpse's max() has nothing stale to pick up.
|
||||
if (forceRespawnTimer > 0s || wasAlive)
|
||||
m_respawnTime = GameTime::GetGameTime().count() + m_respawnDelay;
|
||||
|
||||
RemoveCorpse(true);
|
||||
|
||||
|
||||
@@ -2968,6 +2968,7 @@ public:
|
||||
explicit GameObjectModelOwnerImpl(GameObject* owner) : _owner(owner) { }
|
||||
|
||||
bool IsSpawned() const override { return _owner->isSpawned(); }
|
||||
bool IsTransport() const override { return _owner->IsTransport(); }
|
||||
uint32 GetDisplayId() const override { return _owner->GetDisplayId(); }
|
||||
uint32 GetPhaseMask() const override { return (_owner->GetGoState() == GO_STATE_READY || _owner->IsTransport()) ? _owner->GetPhaseMask() : 0; }
|
||||
G3D::Vector3 GetPosition() const override { return G3D::Vector3(_owner->GetPositionX(), _owner->GetPositionY(), _owner->GetPositionZ()); }
|
||||
|
||||
@@ -26,6 +26,7 @@
|
||||
#include "SpellInfo.h"
|
||||
#include "SpellMgr.h"
|
||||
#include "StringConvert.h"
|
||||
#include "TC9Sidecar.h"
|
||||
#include "Tokenize.h"
|
||||
#include "WorldPacket.h"
|
||||
|
||||
@@ -348,7 +349,7 @@ void Item::SaveToDB(CharacterDatabaseTransaction trans)
|
||||
uint8 index = 0;
|
||||
CharacterDatabasePreparedStatement* stmt = CharacterDatabase.GetPreparedStatement(uState == ITEM_NEW ? CHAR_REP_ITEM_INSTANCE : CHAR_UPD_ITEM_INSTANCE);
|
||||
stmt->SetData( index, GetEntry());
|
||||
stmt->SetData(++index, GetOwnerGUID().GetCounter());
|
||||
stmt->SetData(++index, GetOwnerGUID().GetRawValue());
|
||||
stmt->SetData(++index, GetGuidValue(ITEM_FIELD_CREATOR).GetCounter());
|
||||
stmt->SetData(++index, GetGuidValue(ITEM_FIELD_GIFTCREATOR).GetCounter());
|
||||
stmt->SetData(++index, GetCount());
|
||||
@@ -381,7 +382,7 @@ void Item::SaveToDB(CharacterDatabaseTransaction trans)
|
||||
if ((uState == ITEM_CHANGED) && IsWrapped())
|
||||
{
|
||||
stmt = CharacterDatabase.GetPreparedStatement(CHAR_UPD_GIFT_OWNER);
|
||||
stmt->SetData(0, GetOwnerGUID().GetCounter());
|
||||
stmt->SetData(0, GetOwnerGUID().GetRawValue());
|
||||
stmt->SetData(1, guid);
|
||||
trans->Append(stmt);
|
||||
}
|
||||
@@ -1096,30 +1097,33 @@ Item* Item::CreateItem(uint32 item, uint32 count, Player const* player, bool clo
|
||||
return nullptr; //don't create item at zero count
|
||||
|
||||
ItemTemplate const* pProto = sObjectMgr->GetItemTemplate(item);
|
||||
if (pProto)
|
||||
{
|
||||
if (count > pProto->GetMaxStackSize())
|
||||
count = pProto->GetMaxStackSize();
|
||||
|
||||
ASSERT_NODEBUGINFO(count != 0 && "pProto->Stackable == 0 but checked at loading already");
|
||||
|
||||
Item* pItem = NewItemOrBag(pProto);
|
||||
uint32 guid = temp ? 0xFFFFFFFF : sObjectMgr->GetGenerator<HighGuid::Item>().Generate();
|
||||
if (pItem->Create(guid, item, player))
|
||||
{
|
||||
pItem->SetCount(count);
|
||||
if (!clone)
|
||||
pItem->SetItemRandomProperties(randomPropertyId ? randomPropertyId : Item::GenerateItemRandomPropertyId(item));
|
||||
else if (randomPropertyId)
|
||||
pItem->SetItemRandomProperties(randomPropertyId);
|
||||
return pItem;
|
||||
}
|
||||
else
|
||||
delete pItem;
|
||||
}
|
||||
else
|
||||
if (!pProto)
|
||||
ABORT();
|
||||
return nullptr;
|
||||
|
||||
if (count > pProto->GetMaxStackSize())
|
||||
count = pProto->GetMaxStackSize();
|
||||
|
||||
ASSERT_NODEBUGINFO(count != 0 && "pProto->Stackable == 0 but checked at loading already");
|
||||
|
||||
uint16 realmId = DEFAULT_NON_CROSSREALM_REALM_ID;
|
||||
if (sToCloud9Sidecar->IsCrossrealm() && player)
|
||||
realmId = player->GetGUID().GetRealmID();
|
||||
|
||||
Item* pItem = NewItemOrBag(pProto);
|
||||
uint32 guid = temp ? 0xFFFFFFFF : sObjectMgr->GetGenerator<HighGuid::Item>().Generate(realmId);
|
||||
if (!pItem->Create(guid, item, player))
|
||||
{
|
||||
delete pItem;
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
pItem->SetCount(count);
|
||||
if (!clone)
|
||||
pItem->SetItemRandomProperties(randomPropertyId ? randomPropertyId : Item::GenerateItemRandomPropertyId(item));
|
||||
else if (randomPropertyId)
|
||||
pItem->SetItemRandomProperties(randomPropertyId);
|
||||
|
||||
return pItem;
|
||||
}
|
||||
|
||||
Item* Item::CloneItem(uint32 count, Player const* player) const
|
||||
|
||||
@@ -121,10 +121,14 @@ void Object::_InitValues()
|
||||
}
|
||||
|
||||
void Object::_Create(ObjectGuid::LowType guidlow, uint32 entry, HighGuid guidhigh)
|
||||
{
|
||||
_Create(ObjectGuid(guidhigh, entry, guidlow));
|
||||
}
|
||||
|
||||
void Object::_Create(ObjectGuid guid)
|
||||
{
|
||||
if (!m_uint32Values) _InitValues();
|
||||
|
||||
ObjectGuid guid(guidhigh, entry, guidlow);
|
||||
SetGuidValue(OBJECT_FIELD_GUID, guid);
|
||||
SetUInt32Value(OBJECT_FIELD_TYPE, m_objectType);
|
||||
m_PackGUID.Set(guid);
|
||||
|
||||
@@ -241,6 +241,7 @@ protected:
|
||||
|
||||
void _InitValues();
|
||||
void _Create(ObjectGuid::LowType guidlow, uint32 entry, HighGuid guidhigh);
|
||||
void _Create(ObjectGuid guid);
|
||||
[[nodiscard]] std::string _ConcatFields(uint16 startIndex, uint16 size) const;
|
||||
bool _LoadIntoDataField(std::string const& data, uint32 startOffset, uint32 count);
|
||||
|
||||
|
||||
@@ -17,6 +17,7 @@
|
||||
|
||||
#include "ObjectGuid.h"
|
||||
#include "Log.h"
|
||||
#include "TC9Sidecar.h"
|
||||
#include "World.h"
|
||||
#include <iomanip>
|
||||
#include <sstream>
|
||||
@@ -96,6 +97,21 @@ void ObjectGuidGeneratorBase::HandleCounterOverflow(HighGuid high)
|
||||
World::StopNow(ERROR_EXIT_CODE);
|
||||
}
|
||||
|
||||
bool ObjectGuidGeneratorBase::GetClusterGuid(HighGuid high, uint16 realmId, ObjectGuid::LowType& clusterGuid)
|
||||
{
|
||||
if (!sToCloud9Sidecar->ClusterModeEnabled())
|
||||
return false;
|
||||
|
||||
if (high == HighGuid::Player)
|
||||
clusterGuid = ObjectGuid::LowType(sToCloud9Sidecar->GenerateCharacterGuid(realmId));
|
||||
else if (high == HighGuid::Item)
|
||||
clusterGuid = ObjectGuid::LowType(sToCloud9Sidecar->GenerateItemGuid(realmId));
|
||||
else
|
||||
return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
#define GUID_TRAIT_INSTANTIATE_GUID( HIGH_GUID ) \
|
||||
template class ObjectGuidGenerator< HIGH_GUID >;
|
||||
|
||||
|
||||
@@ -27,6 +27,9 @@
|
||||
#include <unordered_set>
|
||||
#include <vector>
|
||||
|
||||
// Realm id packed into bits 32-47 of a player ObjectGuid; 0 means local / non-crossrealm.
|
||||
constexpr uint16 DEFAULT_NON_CROSSREALM_REALM_ID = 0;
|
||||
|
||||
enum TypeID
|
||||
{
|
||||
TYPEID_OBJECT = 0,
|
||||
@@ -142,6 +145,7 @@ class ObjectGuid
|
||||
[[nodiscard]] uint64 GetRawValue() const { return _guid; }
|
||||
[[nodiscard]] HighGuid GetHigh() const { return HighGuid((_guid >> 48) & 0x0000FFFF); }
|
||||
[[nodiscard]] uint32 GetEntry() const { return HasEntry() ? uint32((_guid >> 24) & UI64LIT(0x0000000000FFFFFF)) : 0; }
|
||||
[[nodiscard]] uint16 GetRealmID() const { return IsPlayer() ? uint16((_guid >> 32) & UI64LIT(0xFFFF)) : 0; }
|
||||
[[nodiscard]] LowType GetCounter() const
|
||||
{
|
||||
return HasEntry()
|
||||
@@ -283,12 +287,13 @@ public:
|
||||
ObjectGuidGeneratorBase(ObjectGuid::LowType start = 1) : _nextGuid(start) { }
|
||||
|
||||
virtual void Set(ObjectGuid::LowType val) { _nextGuid = val; }
|
||||
virtual ObjectGuid::LowType Generate() = 0;
|
||||
virtual ObjectGuid::LowType Generate(uint16 realmId = DEFAULT_NON_CROSSREALM_REALM_ID) = 0;
|
||||
[[nodiscard]] ObjectGuid::LowType GetNextAfterMaxUsed() const { return _nextGuid; }
|
||||
virtual ~ObjectGuidGeneratorBase() = default;
|
||||
|
||||
protected:
|
||||
static void HandleCounterOverflow(HighGuid high);
|
||||
static bool GetClusterGuid(HighGuid high, uint16 realmId, ObjectGuid::LowType& clusterGuid);
|
||||
ObjectGuid::LowType _nextGuid;
|
||||
};
|
||||
|
||||
@@ -298,8 +303,12 @@ class ObjectGuidGenerator : public ObjectGuidGeneratorBase
|
||||
public:
|
||||
explicit ObjectGuidGenerator(ObjectGuid::LowType start = 1) : ObjectGuidGeneratorBase(start) { }
|
||||
|
||||
ObjectGuid::LowType Generate() override
|
||||
ObjectGuid::LowType Generate(uint16 realmId = DEFAULT_NON_CROSSREALM_REALM_ID) override
|
||||
{
|
||||
ObjectGuid::LowType clusterGuid = 0;
|
||||
if (GetClusterGuid(high, realmId, clusterGuid))
|
||||
return clusterGuid;
|
||||
|
||||
if (_nextGuid >= ObjectGuid::GetMaxCounter(high) - 1)
|
||||
HandleCounterOverflow(high);
|
||||
|
||||
|
||||
@@ -3861,7 +3861,7 @@ void Player::_LoadSpellCooldowns(PreparedQueryResult result)
|
||||
void Player::_SaveSpellCooldowns(CharacterDatabaseTransaction trans, bool logout)
|
||||
{
|
||||
CharacterDatabasePreparedStatement* stmt = CharacterDatabase.GetPreparedStatement(CHAR_DEL_CHAR_SPELL_COOLDOWN);
|
||||
stmt->SetData(0, GetGUID().GetCounter());
|
||||
stmt->SetData(0, GetGUID().GetRawValue());
|
||||
trans->Append(stmt);
|
||||
|
||||
time_t curTime = GameTime::GetGameTime().count();
|
||||
@@ -4852,7 +4852,7 @@ void Player::SpawnCorpseBones(bool triggerSave /*= true*/)
|
||||
|
||||
// pussywizard: update only ghost flag instead of whole character table entry! data integrity is crucial
|
||||
CharacterDatabasePreparedStatement* stmt = CharacterDatabase.GetPreparedStatement(CHAR_UPD_CHAR_REMOVE_GHOST);
|
||||
stmt->SetData(0, GetGUID().GetCounter());
|
||||
stmt->SetData(0, GetGUID().GetRawValue());
|
||||
trans->Append(stmt);
|
||||
|
||||
_SaveAuras(trans, false);
|
||||
@@ -6647,7 +6647,7 @@ void Player::ModifyHonorPoints(int32 value, CharacterDatabaseTransaction trans)
|
||||
{
|
||||
CharacterDatabasePreparedStatement* stmt = CharacterDatabase.GetPreparedStatement(CHAR_UDP_CHAR_HONOR_POINTS);
|
||||
stmt->SetData(0, newValue);
|
||||
stmt->SetData(1, GetGUID().GetCounter());
|
||||
stmt->SetData(1, GetGUID().GetRawValue());
|
||||
trans->Append(stmt);
|
||||
}
|
||||
}
|
||||
@@ -6663,7 +6663,7 @@ void Player::ModifyArenaPoints(int32 value, CharacterDatabaseTransaction trans)
|
||||
{
|
||||
CharacterDatabasePreparedStatement* stmt = CharacterDatabase.GetPreparedStatement(CHAR_UDP_CHAR_ARENA_POINTS);
|
||||
stmt->SetData(0, newValue);
|
||||
stmt->SetData(1, GetGUID().GetCounter());
|
||||
stmt->SetData(1, GetGUID().GetRawValue());
|
||||
trans->Append(stmt);
|
||||
}
|
||||
}
|
||||
@@ -9516,7 +9516,7 @@ void Player::RemovePet(Pet* pet, PetSaveMode mode, bool returnreagent)
|
||||
// Handle removing pet while it is in "temporarily unsummoned" state, for example on mount
|
||||
CharacterDatabasePreparedStatement* stmt = CharacterDatabase.GetPreparedStatement(CHAR_UPD_CHAR_PET_SLOT_BY_ID);
|
||||
stmt->SetData(0, PET_SAVE_NOT_IN_SLOT);
|
||||
stmt->SetData(1, GetGUID().GetCounter());
|
||||
stmt->SetData(1, GetGUID().GetRawValue());
|
||||
stmt->SetData(2, m_petStable->CurrentPet->PetNumber);
|
||||
CharacterDatabase.Execute(stmt);
|
||||
|
||||
@@ -11694,7 +11694,7 @@ void Player::LeaveBattleground(Battleground* bg)
|
||||
if (sWorld->getBoolConfig(CONFIG_BATTLEGROUND_TRACK_DESERTERS))
|
||||
{
|
||||
CharacterDatabasePreparedStatement* stmt = CharacterDatabase.GetPreparedStatement(CHAR_INS_DESERTER_TRACK);
|
||||
stmt->SetData(0, GetGUID().GetCounter());
|
||||
stmt->SetData(0, GetGUID().GetRawValue());
|
||||
stmt->SetData(1, BG_DESERTION_TYPE_LEAVE_BG);
|
||||
CharacterDatabase.Execute(stmt);
|
||||
}
|
||||
@@ -14218,7 +14218,7 @@ void Player::_LoadSkills(PreparedQueryResult result)
|
||||
|
||||
CharacterDatabasePreparedStatement* stmt = CharacterDatabase.GetPreparedStatement(CHAR_DEL_CHARACTER_SKILL);
|
||||
|
||||
stmt->SetData(0, GetGUID().GetCounter());
|
||||
stmt->SetData(0, GetGUID().GetRawValue());
|
||||
stmt->SetData(1, skill);
|
||||
|
||||
CharacterDatabase.Execute(stmt);
|
||||
@@ -15163,7 +15163,7 @@ void Player::_SaveEquipmentSets(CharacterDatabaseTransaction trans)
|
||||
stmt->SetData(j++, eqset.IgnoreMask);
|
||||
for (uint8 i = 0; i < EQUIPMENT_SLOT_END; ++i)
|
||||
stmt->SetData(j++, eqset.Items[i].GetCounter());
|
||||
stmt->SetData(j++, GetGUID().GetCounter());
|
||||
stmt->SetData(j++, GetGUID().GetRawValue());
|
||||
stmt->SetData(j++, eqset.Guid);
|
||||
stmt->SetData(j, index);
|
||||
trans->Append(stmt);
|
||||
@@ -15172,7 +15172,7 @@ void Player::_SaveEquipmentSets(CharacterDatabaseTransaction trans)
|
||||
break;
|
||||
case EQUIPMENT_SET_NEW:
|
||||
stmt = CharacterDatabase.GetPreparedStatement(CHAR_INS_EQUIP_SET);
|
||||
stmt->SetData(j++, GetGUID().GetCounter());
|
||||
stmt->SetData(j++, GetGUID().GetRawValue());
|
||||
stmt->SetData(j++, eqset.Guid);
|
||||
stmt->SetData(j++, index);
|
||||
stmt->SetData(j++, eqset.Name.c_str());
|
||||
@@ -15202,11 +15202,11 @@ void Player::_SaveEntryPoint(CharacterDatabaseTransaction trans)
|
||||
return;
|
||||
|
||||
CharacterDatabasePreparedStatement* stmt = CharacterDatabase.GetPreparedStatement(CHAR_DEL_PLAYER_ENTRY_POINT);
|
||||
stmt->SetData(0, GetGUID().GetCounter());
|
||||
stmt->SetData(0, GetGUID().GetRawValue());
|
||||
trans->Append(stmt);
|
||||
|
||||
stmt = CharacterDatabase.GetPreparedStatement(CHAR_INS_PLAYER_ENTRY_POINT);
|
||||
stmt->SetData(0, GetGUID().GetCounter());
|
||||
stmt->SetData(0, GetGUID().GetRawValue());
|
||||
stmt->SetData (1, m_entryPointData.joinPos.GetPositionX());
|
||||
stmt->SetData (2, m_entryPointData.joinPos.GetPositionY());
|
||||
stmt->SetData (3, m_entryPointData.joinPos.GetPositionZ());
|
||||
@@ -15243,7 +15243,7 @@ void Player::RemoveAtLoginFlag(AtLoginFlags flags, bool persist /*= false*/)
|
||||
CharacterDatabasePreparedStatement* stmt = CharacterDatabase.GetPreparedStatement(CHAR_UPD_REM_AT_LOGIN_FLAG);
|
||||
|
||||
stmt->SetData(0, uint16(flags));
|
||||
stmt->SetData(1, GetGUID().GetCounter());
|
||||
stmt->SetData(1, GetGUID().GetRawValue());
|
||||
|
||||
CharacterDatabase.Execute(stmt);
|
||||
}
|
||||
@@ -15309,7 +15309,7 @@ void Player::_SaveCharacter(bool create, CharacterDatabaseTransaction trans)
|
||||
//! Insert query
|
||||
//! TO DO: Filter out more redundant fields that can take their default value at player create
|
||||
stmt = CharacterDatabase.GetPreparedStatement(CHAR_INS_CHARACTER);
|
||||
stmt->SetData(index++, GetGUID().GetCounter());
|
||||
stmt->SetData(index++, GetGUID().GetRawValue());
|
||||
stmt->SetData(index++, GetSession()->GetAccountId());
|
||||
stmt->SetData(index++, GetName());
|
||||
stmt->SetData(index++, getRace(true));
|
||||
@@ -15567,7 +15567,7 @@ void Player::_SaveCharacter(bool create, CharacterDatabaseTransaction trans)
|
||||
|
||||
stmt->SetData(index++, IsInWorld() && !GetSession()->PlayerLogout() ? 1 : 0);
|
||||
// Index
|
||||
stmt->SetData(index++, GetGUID().GetCounter());
|
||||
stmt->SetData(index++, GetGUID().GetRawValue());
|
||||
}
|
||||
|
||||
trans->Append(stmt);
|
||||
@@ -15616,7 +15616,7 @@ void Player::_SaveGlyphs(CharacterDatabaseTransaction trans)
|
||||
return;
|
||||
|
||||
CharacterDatabasePreparedStatement* stmt = CharacterDatabase.GetPreparedStatement(CHAR_DEL_CHAR_GLYPHS);
|
||||
stmt->SetData(0, GetGUID().GetCounter());
|
||||
stmt->SetData(0, GetGUID().GetRawValue());
|
||||
trans->Append(stmt);
|
||||
|
||||
for (uint8 spec = 0; spec < m_specsCount; ++spec)
|
||||
@@ -15624,7 +15624,7 @@ void Player::_SaveGlyphs(CharacterDatabaseTransaction trans)
|
||||
uint8 index = 0;
|
||||
|
||||
stmt = CharacterDatabase.GetPreparedStatement(CHAR_INS_CHAR_GLYPHS);
|
||||
stmt->SetData(index++, GetGUID().GetCounter());
|
||||
stmt->SetData(index++, GetGUID().GetRawValue());
|
||||
stmt->SetData(index++, spec);
|
||||
|
||||
for (uint8 i = 0; i < MAX_GLYPH_SLOT_INDEX; ++i)
|
||||
@@ -15671,7 +15671,7 @@ void Player::_SaveTalents(CharacterDatabaseTransaction trans)
|
||||
if (itr->second->State == PLAYERSPELL_REMOVED || itr->second->State == PLAYERSPELL_CHANGED)
|
||||
{
|
||||
stmt = CharacterDatabase.GetPreparedStatement(CHAR_DEL_CHAR_TALENT_BY_SPELL);
|
||||
stmt->SetData(0, GetGUID().GetCounter());
|
||||
stmt->SetData(0, GetGUID().GetRawValue());
|
||||
stmt->SetData(1, itr->first);
|
||||
trans->Append(stmt);
|
||||
}
|
||||
@@ -15680,7 +15680,7 @@ void Player::_SaveTalents(CharacterDatabaseTransaction trans)
|
||||
if (itr->second->State == PLAYERSPELL_NEW || itr->second->State == PLAYERSPELL_CHANGED)
|
||||
{
|
||||
stmt = CharacterDatabase.GetPreparedStatement(CHAR_INS_CHAR_TALENT);
|
||||
stmt->SetData(0, GetGUID().GetCounter());
|
||||
stmt->SetData(0, GetGUID().GetRawValue());
|
||||
stmt->SetData(1, itr->first);
|
||||
stmt->SetData(2, itr->second->specMask);
|
||||
trans->Append(stmt);
|
||||
@@ -15853,7 +15853,7 @@ void Player::ActivateSpec(uint8 spec)
|
||||
// load them asynchronously
|
||||
{
|
||||
CharacterDatabasePreparedStatement* stmt = CharacterDatabase.GetPreparedStatement(CHAR_SEL_CHARACTER_ACTIONS_SPEC);
|
||||
stmt->SetData(0, GetGUID().GetCounter());
|
||||
stmt->SetData(0, GetGUID().GetRawValue());
|
||||
stmt->SetData(1, m_activeSpec);
|
||||
|
||||
WorldSession* mySess = GetSession();
|
||||
@@ -16368,7 +16368,7 @@ void Player::SetRandomWinner(bool isWinner)
|
||||
if (m_IsBGRandomWinner)
|
||||
{
|
||||
CharacterDatabasePreparedStatement* stmt = CharacterDatabase.GetPreparedStatement(CHAR_INS_BATTLEGROUND_RANDOM);
|
||||
stmt->SetData(0, GetGUID().GetCounter());
|
||||
stmt->SetData(0, GetGUID().GetRawValue());
|
||||
CharacterDatabase.Execute(stmt);
|
||||
}
|
||||
}
|
||||
@@ -16485,7 +16485,7 @@ void Player::_LoadBrewOfTheMonth(PreparedQueryResult result)
|
||||
|
||||
// Update Event Id
|
||||
CharacterDatabasePreparedStatement* stmt = CharacterDatabase.GetPreparedStatement(CHAR_REP_BREW_OF_THE_MONTH);
|
||||
stmt->SetData(0, GetGUID().GetCounter());
|
||||
stmt->SetData(0, GetGUID().GetRawValue());
|
||||
stmt->SetData(1, uint32(eventId));
|
||||
trans->Append(stmt);
|
||||
|
||||
|
||||
@@ -995,6 +995,7 @@ enum AdditionalSaving
|
||||
ADDITIONAL_SAVING_NONE = 0x00,
|
||||
ADDITIONAL_SAVING_INVENTORY_AND_GOLD = 0x01,
|
||||
ADDITIONAL_SAVING_QUEST_STATUS = 0x02,
|
||||
ADDITIONAL_SAVING_ACHIEVEMENTS = 0x04,
|
||||
};
|
||||
|
||||
enum PlayerCommandStates
|
||||
@@ -2638,7 +2639,7 @@ public:
|
||||
bool IsFreeFlying() const { return HasAuraType(SPELL_AURA_MOD_INCREASE_MOUNTED_FLIGHT_SPEED) || HasAuraType(SPELL_AURA_FLY); }
|
||||
|
||||
// saving
|
||||
void AdditionalSavingAddMask(uint8 mask) { m_additionalSaveTimer = 2000; m_additionalSaveMask |= mask; }
|
||||
void AdditionalSavingAddMask(uint8 mask);
|
||||
// arena spectator
|
||||
[[nodiscard]] bool IsSpectator() const { return m_ExtraFlags & PLAYER_EXTRA_SPECTATOR_ON; }
|
||||
void SetIsSpectator(bool on);
|
||||
@@ -2836,6 +2837,7 @@ protected:
|
||||
void _SaveCharacter(bool create, CharacterDatabaseTransaction trans);
|
||||
void _SaveInstanceTimeRestrictions(CharacterDatabaseTransaction trans);
|
||||
void _SavePlayerSettings(CharacterDatabaseTransaction trans);
|
||||
void UpdateAdditionalSaves(uint32 p_time);
|
||||
|
||||
/*********************************************************/
|
||||
/*** ENVIRONMENTAL SYSTEM ***/
|
||||
|
||||
@@ -5004,7 +5004,7 @@ void Player::SetHomebind(WorldLocation const& loc, uint32 areaId)
|
||||
stmt->SetData (2, m_homebindX);
|
||||
stmt->SetData (3, m_homebindY);
|
||||
stmt->SetData (4, m_homebindZ);
|
||||
stmt->SetData(5, GetGUID().GetCounter());
|
||||
stmt->SetData(5, GetGUID().GetRawValue());
|
||||
CharacterDatabase.Execute(stmt);
|
||||
}
|
||||
|
||||
@@ -5053,9 +5053,9 @@ bool Player::LoadFromDB(ObjectGuid playerGuid, CharacterDatabaseQueryHolder cons
|
||||
return false;
|
||||
}
|
||||
|
||||
ObjectGuid::LowType guid = playerGuid.GetCounter();
|
||||
uint64 guid = playerGuid.GetRawValue();
|
||||
|
||||
Object::_Create(guid, 0, HighGuid::Player);
|
||||
Object::_Create(playerGuid);
|
||||
|
||||
m_name = fields[2].Get<std::string>();
|
||||
|
||||
@@ -6147,7 +6147,7 @@ Item* Player::_LoadItem(CharacterDatabaseTransaction trans, uint32 zoneId, uint3
|
||||
// xinef: sync query
|
||||
stmt = CharacterDatabase.GetPreparedStatement(CHAR_SEL_ITEM_REFUNDS);
|
||||
stmt->SetData(0, item->GetGUID().GetCounter());
|
||||
stmt->SetData(1, GetGUID().GetCounter());
|
||||
stmt->SetData(1, GetGUID().GetRawValue());
|
||||
if (PreparedQueryResult result = CharacterDatabase.Query(stmt))
|
||||
{
|
||||
item->SetRefundRecipient((*result)[0].Get<uint32>());
|
||||
@@ -7199,7 +7199,7 @@ bool Player::_LoadHomeBind(PreparedQueryResult result)
|
||||
else
|
||||
{
|
||||
CharacterDatabasePreparedStatement* stmt = CharacterDatabase.GetPreparedStatement(CHAR_DEL_PLAYER_HOMEBIND);
|
||||
stmt->SetData(0, GetGUID().GetCounter());
|
||||
stmt->SetData(0, GetGUID().GetRawValue());
|
||||
CharacterDatabase.Execute(stmt);
|
||||
}
|
||||
}
|
||||
@@ -7232,7 +7232,7 @@ bool Player::_LoadHomeBind(PreparedQueryResult result)
|
||||
m_homebindZ = info->positionZ;
|
||||
|
||||
CharacterDatabasePreparedStatement* stmt = CharacterDatabase.GetPreparedStatement(CHAR_INS_PLAYER_HOMEBIND);
|
||||
stmt->SetData(0, GetGUID().GetCounter());
|
||||
stmt->SetData(0, GetGUID().GetRawValue());
|
||||
stmt->SetData(1, m_homebindMapId);
|
||||
stmt->SetData(2, m_homebindAreaId);
|
||||
stmt->SetData (3, m_homebindX);
|
||||
@@ -7320,6 +7320,18 @@ void Player::SaveToDB(CharacterDatabaseTransaction trans, bool create, bool logo
|
||||
pet->SavePetToDB(PET_SAVE_AS_CURRENT);
|
||||
}
|
||||
|
||||
// flag data to be saved by UpdateAdditionalSaves a moment after an important change,
|
||||
// filtered by the PlayerSave.AdditionalSaves config mask
|
||||
void Player::AdditionalSavingAddMask(uint8 mask)
|
||||
{
|
||||
mask &= sWorld->getIntConfig(CONFIG_ADDITIONAL_SAVES);
|
||||
if (!mask)
|
||||
return;
|
||||
|
||||
m_additionalSaveTimer = 2000;
|
||||
m_additionalSaveMask |= mask;
|
||||
}
|
||||
|
||||
// fast save function for item/money cheating preventing - save only inventory and money state
|
||||
void Player::SaveInventoryAndGoldToDB(CharacterDatabaseTransaction trans)
|
||||
{
|
||||
@@ -7331,7 +7343,7 @@ void Player::SaveGoldToDB(CharacterDatabaseTransaction trans)
|
||||
{
|
||||
CharacterDatabasePreparedStatement* stmt = CharacterDatabase.GetPreparedStatement(CHAR_UDP_CHAR_MONEY);
|
||||
stmt->SetData(0, GetMoney());
|
||||
stmt->SetData(1, GetGUID().GetCounter());
|
||||
stmt->SetData(1, GetGUID().GetRawValue());
|
||||
trans->Append(stmt);
|
||||
}
|
||||
|
||||
@@ -7345,7 +7357,7 @@ void Player::_SaveActions(CharacterDatabaseTransaction trans)
|
||||
{
|
||||
case ACTIONBUTTON_NEW:
|
||||
stmt = CharacterDatabase.GetPreparedStatement(CHAR_INS_CHAR_ACTION);
|
||||
stmt->SetData(0, GetGUID().GetCounter());
|
||||
stmt->SetData(0, GetGUID().GetRawValue());
|
||||
stmt->SetData(1, m_activeSpec);
|
||||
stmt->SetData(2, itr->first);
|
||||
stmt->SetData(3, itr->second.GetAction());
|
||||
@@ -7359,7 +7371,7 @@ void Player::_SaveActions(CharacterDatabaseTransaction trans)
|
||||
stmt = CharacterDatabase.GetPreparedStatement(CHAR_UPD_CHAR_ACTION);
|
||||
stmt->SetData(0, itr->second.GetAction());
|
||||
stmt->SetData(1, uint8(itr->second.GetType()));
|
||||
stmt->SetData(2, GetGUID().GetCounter());
|
||||
stmt->SetData(2, GetGUID().GetRawValue());
|
||||
stmt->SetData(3, itr->first);
|
||||
stmt->SetData(4, m_activeSpec);
|
||||
trans->Append(stmt);
|
||||
@@ -7369,7 +7381,7 @@ void Player::_SaveActions(CharacterDatabaseTransaction trans)
|
||||
break;
|
||||
case ACTIONBUTTON_DELETED:
|
||||
stmt = CharacterDatabase.GetPreparedStatement(CHAR_DEL_CHAR_ACTION_BY_BUTTON_SPEC);
|
||||
stmt->SetData(0, GetGUID().GetCounter());
|
||||
stmt->SetData(0, GetGUID().GetRawValue());
|
||||
stmt->SetData(1, itr->first);
|
||||
stmt->SetData(2, m_activeSpec);
|
||||
trans->Append(stmt);
|
||||
@@ -7386,7 +7398,7 @@ void Player::_SaveActions(CharacterDatabaseTransaction trans)
|
||||
void Player::_SaveAuras(CharacterDatabaseTransaction trans, bool logout)
|
||||
{
|
||||
CharacterDatabasePreparedStatement* stmt = CharacterDatabase.GetPreparedStatement(CHAR_DEL_CHAR_AURA);
|
||||
stmt->SetData(0, GetGUID().GetCounter());
|
||||
stmt->SetData(0, GetGUID().GetRawValue());
|
||||
trans->Append(stmt);
|
||||
|
||||
for (AuraMap::const_iterator itr = m_ownedAuras.begin(); itr != m_ownedAuras.end(); ++itr)
|
||||
@@ -7421,7 +7433,7 @@ void Player::_SaveAuras(CharacterDatabaseTransaction trans, bool logout)
|
||||
|
||||
uint8 index = 0;
|
||||
stmt = CharacterDatabase.GetPreparedStatement(CHAR_INS_AURA);
|
||||
stmt->SetData(index++, GetGUID().GetCounter());
|
||||
stmt->SetData(index++, GetGUID().GetRawValue());
|
||||
stmt->SetData(index++, itr->second->GetCasterGUID().GetRawValue());
|
||||
stmt->SetData(index++, itr->second->GetCastItemGUID().GetRawValue());
|
||||
stmt->SetData(index++, itr->second->GetId());
|
||||
@@ -7506,7 +7518,7 @@ void Player::_SaveInventory(CharacterDatabaseTransaction trans)
|
||||
if (m_itemUpdateQueue.empty())
|
||||
return;
|
||||
|
||||
ObjectGuid::LowType lowGuid = GetGUID().GetCounter();
|
||||
uint64 guid = GetGUID().GetRawValue();
|
||||
for (std::size_t i = 0; i < m_itemUpdateQueue.size(); ++i)
|
||||
{
|
||||
Item* item = m_itemUpdateQueue[i];
|
||||
@@ -7525,12 +7537,12 @@ void Player::_SaveInventory(CharacterDatabaseTransaction trans)
|
||||
if (Item* test2 = GetItemByPos(INVENTORY_SLOT_BAG_0, item->GetBagSlot()))
|
||||
bagTestGUID = test2->GetGUID().GetCounter();
|
||||
LOG_ERROR("entities.player", "Player(GUID: {} Name: {})::_SaveInventory - the bag({}) and slot({}) values for the item {} (state {}) are incorrect, the player doesn't have an item at that position!",
|
||||
lowGuid, GetName(), item->GetBagSlot(), item->GetSlot(), item->GetGUID().ToString(), (int32)item->GetState());
|
||||
guid, GetName(), item->GetBagSlot(), item->GetSlot(), item->GetGUID().ToString(), (int32)item->GetState());
|
||||
// according to the test that was just performed nothing should be in this slot, delete
|
||||
stmt = CharacterDatabase.GetPreparedStatement(CHAR_DEL_CHAR_INVENTORY_BY_BAG_SLOT);
|
||||
stmt->SetData(0, bagTestGUID);
|
||||
stmt->SetData(1, item->GetSlot());
|
||||
stmt->SetData(2, lowGuid);
|
||||
stmt->SetData(2, guid);
|
||||
trans->Append(stmt);
|
||||
|
||||
RemoveTradeableItem(item); // pussywizard
|
||||
@@ -7546,7 +7558,7 @@ void Player::_SaveInventory(CharacterDatabaseTransaction trans)
|
||||
else if (test != item)
|
||||
{
|
||||
LOG_ERROR("entities.player", "Player(GUID: {} Name: {})::_SaveInventory - the bag({}) and slot({}) values for the item ({}) are incorrect, the item ({}) is there instead!",
|
||||
lowGuid, GetName(), item->GetBagSlot(), item->GetSlot(), item->GetGUID().ToString(), test->GetGUID().ToString());
|
||||
guid, GetName(), item->GetBagSlot(), item->GetSlot(), item->GetGUID().ToString(), test->GetGUID().ToString());
|
||||
// save all changes to the item...
|
||||
if (item->GetState() != ITEM_NEW) // only for existing items, no dupes
|
||||
item->SaveToDB(trans);
|
||||
@@ -7560,7 +7572,7 @@ void Player::_SaveInventory(CharacterDatabaseTransaction trans)
|
||||
case ITEM_NEW:
|
||||
case ITEM_CHANGED:
|
||||
stmt = CharacterDatabase.GetPreparedStatement(CHAR_REP_INVENTORY_ITEM);
|
||||
stmt->SetData(0, lowGuid);
|
||||
stmt->SetData(0, guid);
|
||||
stmt->SetData(1, bag_guid);
|
||||
stmt->SetData (2, item->GetSlot());
|
||||
stmt->SetData(3, item->GetGUID().GetCounter());
|
||||
@@ -7676,7 +7688,7 @@ void Player::_SaveQuestStatus(CharacterDatabaseTransaction trans)
|
||||
uint8 index = 0;
|
||||
stmt = CharacterDatabase.GetPreparedStatement(CHAR_REP_CHAR_QUESTSTATUS);
|
||||
|
||||
stmt->SetData(index++, GetGUID().GetCounter());
|
||||
stmt->SetData(index++, GetGUID().GetRawValue());
|
||||
stmt->SetData(index++, statusItr->first);
|
||||
stmt->SetData(index++, uint8(statusItr->second.Status));
|
||||
stmt->SetData(index++, statusItr->second.Explored);
|
||||
@@ -7695,7 +7707,7 @@ void Player::_SaveQuestStatus(CharacterDatabaseTransaction trans)
|
||||
else
|
||||
{
|
||||
stmt = CharacterDatabase.GetPreparedStatement(CHAR_DEL_CHAR_QUESTSTATUS_BY_QUEST);
|
||||
stmt->SetData(0, GetGUID().GetCounter());
|
||||
stmt->SetData(0, GetGUID().GetRawValue());
|
||||
stmt->SetData(1, saveItr->first);
|
||||
trans->Append(stmt);
|
||||
}
|
||||
@@ -7710,7 +7722,7 @@ void Player::_SaveQuestStatus(CharacterDatabaseTransaction trans)
|
||||
else // xinef: what the is this? quest can be removed by spelleffect if (!keepAbandoned)
|
||||
stmt = CharacterDatabase.GetPreparedStatement(CHAR_DEL_CHAR_QUESTSTATUS_REWARDED_BY_QUEST);
|
||||
|
||||
stmt->SetData(0, GetGUID().GetCounter());
|
||||
stmt->SetData(0, GetGUID().GetRawValue());
|
||||
stmt->SetData(1, saveItr->first);
|
||||
trans->Append(stmt);
|
||||
}
|
||||
@@ -7732,14 +7744,14 @@ void Player::_SaveDailyQuestStatus(CharacterDatabaseTransaction trans)
|
||||
|
||||
// we don't need transactions here.
|
||||
CharacterDatabasePreparedStatement* stmt = CharacterDatabase.GetPreparedStatement(CHAR_DEL_QUEST_STATUS_DAILY_CHAR);
|
||||
stmt->SetData(0, GetGUID().GetCounter());
|
||||
stmt->SetData(0, GetGUID().GetRawValue());
|
||||
trans->Append(stmt);
|
||||
for (uint32 quest_daily_idx = 0; quest_daily_idx < PLAYER_MAX_DAILY_QUESTS; ++quest_daily_idx)
|
||||
{
|
||||
if (GetUInt32Value(PLAYER_FIELD_DAILY_QUESTS_1 + quest_daily_idx))
|
||||
{
|
||||
stmt = CharacterDatabase.GetPreparedStatement(CHAR_INS_CHARACTER_DAILYQUESTSTATUS);
|
||||
stmt->SetData(0, GetGUID().GetCounter());
|
||||
stmt->SetData(0, GetGUID().GetRawValue());
|
||||
stmt->SetData(1, GetUInt32Value(PLAYER_FIELD_DAILY_QUESTS_1 + quest_daily_idx));
|
||||
stmt->SetData(2, uint64(m_lastDailyQuestTime));
|
||||
trans->Append(stmt);
|
||||
@@ -7751,7 +7763,7 @@ void Player::_SaveDailyQuestStatus(CharacterDatabaseTransaction trans)
|
||||
for (DFQuestsDoneList::iterator itr = m_DFQuests.begin(); itr != m_DFQuests.end(); ++itr)
|
||||
{
|
||||
stmt = CharacterDatabase.GetPreparedStatement(CHAR_INS_CHARACTER_DAILYQUESTSTATUS);
|
||||
stmt->SetData(0, GetGUID().GetCounter());
|
||||
stmt->SetData(0, GetGUID().GetRawValue());
|
||||
stmt->SetData(1, (*itr));
|
||||
stmt->SetData(2, uint64(m_lastDailyQuestTime));
|
||||
trans->Append(stmt);
|
||||
@@ -7766,7 +7778,7 @@ void Player::_SaveWeeklyQuestStatus(CharacterDatabaseTransaction trans)
|
||||
|
||||
// we don't need transactions here.
|
||||
CharacterDatabasePreparedStatement* stmt = CharacterDatabase.GetPreparedStatement(CHAR_DEL_QUEST_STATUS_WEEKLY_CHAR);
|
||||
stmt->SetData(0, GetGUID().GetCounter());
|
||||
stmt->SetData(0, GetGUID().GetRawValue());
|
||||
trans->Append(stmt);
|
||||
|
||||
for (QuestSet::const_iterator iter = m_weeklyquests.begin(); iter != m_weeklyquests.end(); ++iter)
|
||||
@@ -7774,7 +7786,7 @@ void Player::_SaveWeeklyQuestStatus(CharacterDatabaseTransaction trans)
|
||||
uint32 quest_id = *iter;
|
||||
|
||||
stmt = CharacterDatabase.GetPreparedStatement(CHAR_INS_CHARACTER_WEEKLYQUESTSTATUS);
|
||||
stmt->SetData(0, GetGUID().GetCounter());
|
||||
stmt->SetData(0, GetGUID().GetRawValue());
|
||||
stmt->SetData(1, quest_id);
|
||||
trans->Append(stmt);
|
||||
}
|
||||
@@ -7791,7 +7803,7 @@ void Player::_SaveSeasonalQuestStatus(CharacterDatabaseTransaction trans)
|
||||
|
||||
// we don't need transactions here.
|
||||
CharacterDatabasePreparedStatement* stmt = CharacterDatabase.GetPreparedStatement(CHAR_DEL_QUEST_STATUS_SEASONAL_CHAR);
|
||||
stmt->SetData(0, GetGUID().GetCounter());
|
||||
stmt->SetData(0, GetGUID().GetRawValue());
|
||||
trans->Append(stmt);
|
||||
|
||||
m_SeasonalQuestChanged = false;
|
||||
@@ -7810,7 +7822,7 @@ void Player::_SaveSeasonalQuestStatus(CharacterDatabaseTransaction trans)
|
||||
uint32 questId = *itr;
|
||||
|
||||
stmt = CharacterDatabase.GetPreparedStatement(CHAR_INS_CHARACTER_SEASONALQUESTSTATUS);
|
||||
stmt->SetArguments(GetGUID().GetCounter(), questId, eventId);
|
||||
stmt->SetArguments(GetGUID().GetRawValue(), questId, eventId);
|
||||
trans->Append(stmt);
|
||||
}
|
||||
}
|
||||
@@ -7823,14 +7835,14 @@ void Player::_SaveMonthlyQuestStatus(CharacterDatabaseTransaction trans)
|
||||
|
||||
// we don't need transactions here.
|
||||
CharacterDatabasePreparedStatement* stmt = CharacterDatabase.GetPreparedStatement(CHAR_DEL_QUEST_STATUS_MONTHLY_CHAR);
|
||||
stmt->SetData(0, GetGUID().GetCounter());
|
||||
stmt->SetData(0, GetGUID().GetRawValue());
|
||||
trans->Append(stmt);
|
||||
|
||||
for (QuestSet::const_iterator iter = m_monthlyquests.begin(); iter != m_monthlyquests.end(); ++iter)
|
||||
{
|
||||
uint32 quest_id = *iter;
|
||||
stmt = CharacterDatabase.GetPreparedStatement(CHAR_INS_CHARACTER_MONTHLYQUESTSTATUS);
|
||||
stmt->SetData(0, GetGUID().GetCounter());
|
||||
stmt->SetData(0, GetGUID().GetRawValue());
|
||||
stmt->SetData(1, quest_id);
|
||||
trans->Append(stmt);
|
||||
}
|
||||
@@ -7853,7 +7865,7 @@ void Player::_SaveSkills(CharacterDatabaseTransaction trans)
|
||||
if (itr->second.uState == SKILL_DELETED)
|
||||
{
|
||||
stmt = CharacterDatabase.GetPreparedStatement(CHAR_DEL_CHAR_SKILL_BY_SKILL);
|
||||
stmt->SetData(0, GetGUID().GetCounter());
|
||||
stmt->SetData(0, GetGUID().GetRawValue());
|
||||
stmt->SetData(1, itr->first);
|
||||
trans->Append(stmt);
|
||||
|
||||
@@ -7869,7 +7881,7 @@ void Player::_SaveSkills(CharacterDatabaseTransaction trans)
|
||||
{
|
||||
case SKILL_NEW:
|
||||
stmt = CharacterDatabase.GetPreparedStatement(CHAR_INS_CHAR_SKILLS);
|
||||
stmt->SetData(0, GetGUID().GetCounter());
|
||||
stmt->SetData(0, GetGUID().GetRawValue());
|
||||
stmt->SetData(1, uint16(itr->first));
|
||||
stmt->SetData(2, value);
|
||||
stmt->SetData(3, max);
|
||||
@@ -7880,7 +7892,7 @@ void Player::_SaveSkills(CharacterDatabaseTransaction trans)
|
||||
stmt = CharacterDatabase.GetPreparedStatement(CHAR_UDP_CHAR_SKILLS);
|
||||
stmt->SetData(0, value);
|
||||
stmt->SetData(1, max);
|
||||
stmt->SetData(2, GetGUID().GetCounter());
|
||||
stmt->SetData(2, GetGUID().GetRawValue());
|
||||
stmt->SetData(3, uint16(itr->first));
|
||||
trans->Append(stmt);
|
||||
|
||||
@@ -7911,7 +7923,7 @@ void Player::_SaveSpells(CharacterDatabaseTransaction trans)
|
||||
if (itr->second->State == PLAYERSPELL_REMOVED || itr->second->State == PLAYERSPELL_CHANGED)
|
||||
{
|
||||
stmt = CharacterDatabase.GetPreparedStatement(CHAR_DEL_CHAR_SPELL_BY_SPELL);
|
||||
stmt->SetData(0, GetGUID().GetCounter());
|
||||
stmt->SetData(0, GetGUID().GetRawValue());
|
||||
stmt->SetData(1, itr->first);
|
||||
trans->Append(stmt);
|
||||
}
|
||||
@@ -7920,7 +7932,7 @@ void Player::_SaveSpells(CharacterDatabaseTransaction trans)
|
||||
if (itr->second->State == PLAYERSPELL_NEW || itr->second->State == PLAYERSPELL_CHANGED)
|
||||
{
|
||||
stmt = CharacterDatabase.GetPreparedStatement(CHAR_INS_CHAR_SPELL);
|
||||
stmt->SetData(0, GetGUID().GetCounter());
|
||||
stmt->SetData(0, GetGUID().GetRawValue());
|
||||
stmt->SetData(1, itr->first);
|
||||
stmt->SetData(2, itr->second->specMask);
|
||||
trans->Append(stmt);
|
||||
@@ -7950,13 +7962,13 @@ void Player::_SaveStats(CharacterDatabaseTransaction trans)
|
||||
CharacterDatabasePreparedStatement* stmt = nullptr;
|
||||
|
||||
stmt = CharacterDatabase.GetPreparedStatement(CHAR_DEL_CHAR_STATS);
|
||||
stmt->SetData(0, GetGUID().GetCounter());
|
||||
stmt->SetData(0, GetGUID().GetRawValue());
|
||||
trans->Append(stmt);
|
||||
|
||||
uint8 index = 0;
|
||||
|
||||
stmt = CharacterDatabase.GetPreparedStatement(CHAR_INS_CHAR_STATS);
|
||||
stmt->SetData(index++, GetGUID().GetCounter());
|
||||
stmt->SetData(index++, GetGUID().GetRawValue());
|
||||
stmt->SetData(index++, GetMaxHealth());
|
||||
|
||||
for (uint8 i = 0; i < MAX_POWERS; ++i)
|
||||
|
||||
@@ -332,6 +332,8 @@ void Player::Update(uint32 p_time)
|
||||
}
|
||||
}
|
||||
|
||||
UpdateAdditionalSaves(p_time);
|
||||
|
||||
// Handle Water/drowning
|
||||
HandleDrowning(p_time);
|
||||
|
||||
@@ -2400,3 +2402,49 @@ void Player::ProcessSpellQueue()
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// save only the data flagged by AdditionalSavingAddMask shortly after
|
||||
// important changes, so a crash loses at most a few seconds of them
|
||||
void Player::UpdateAdditionalSaves(uint32 p_time)
|
||||
{
|
||||
if (!m_additionalSaveTimer || GetSession()->isLogingOut())
|
||||
return;
|
||||
|
||||
if (m_additionalSaveTimer > p_time)
|
||||
{
|
||||
m_additionalSaveTimer -= p_time;
|
||||
return;
|
||||
}
|
||||
|
||||
uint8 mask = m_additionalSaveMask;
|
||||
m_additionalSaveTimer = 0;
|
||||
m_additionalSaveMask = 0;
|
||||
|
||||
CharacterDatabaseTransaction trans = CharacterDatabase.BeginTransaction();
|
||||
|
||||
if (mask & ADDITIONAL_SAVING_INVENTORY_AND_GOLD)
|
||||
SaveInventoryAndGoldToDB(trans);
|
||||
|
||||
if (mask & ADDITIONAL_SAVING_QUEST_STATUS)
|
||||
{
|
||||
_SaveQuestStatus(trans);
|
||||
|
||||
// if nothing changed, nothing will happen
|
||||
_SaveDailyQuestStatus(trans);
|
||||
_SaveWeeklyQuestStatus(trans);
|
||||
_SaveSeasonalQuestStatus(trans);
|
||||
_SaveMonthlyQuestStatus(trans);
|
||||
}
|
||||
|
||||
if (mask & ADDITIONAL_SAVING_ACHIEVEMENTS)
|
||||
{
|
||||
m_achievementMgr->SaveToDB(trans);
|
||||
|
||||
// achievements are often earned together with skill or gold changes
|
||||
// (professions, riding, wealth), save those too to keep the DB consistent
|
||||
_SaveSkills(trans);
|
||||
SaveGoldToDB(trans);
|
||||
}
|
||||
|
||||
CharacterDatabase.CommitTransaction(trans);
|
||||
}
|
||||
|
||||
@@ -19,6 +19,7 @@
|
||||
#include "Cell.h"
|
||||
#include "CellImpl.h"
|
||||
#include "Common.h"
|
||||
#include "Config.h"
|
||||
#include "DBCStores.h"
|
||||
#include "GameObjectAI.h"
|
||||
#include "GameTime.h"
|
||||
@@ -30,8 +31,28 @@
|
||||
#include "Spell.h"
|
||||
#include "Vehicle.h"
|
||||
#include "WorldModel.h"
|
||||
#include <chrono>
|
||||
|
||||
MotionTransport::MotionTransport() : Transport(), _transportInfo(nullptr), _isMoving(true), _pendingStop(false), _triggeredArrivalEvent(false), _triggeredDepartureEvent(false), _passengersLoaded(false), _delayedTeleport(false)
|
||||
namespace
|
||||
{
|
||||
// Any fixed reference epoch works; this one is 2023-07-12 05:20:00 UTC.
|
||||
std::time_t const startTimestamp = 1689139200;
|
||||
std::chrono::system_clock::time_point const transportStartDate = std::chrono::system_clock::from_time_t(startTimestamp);
|
||||
|
||||
// Calculates time of the next departure cycle.
|
||||
std::chrono::system_clock::time_point calculateNextDepartureTime(int oneIterationInterval)
|
||||
{
|
||||
std::chrono::system_clock::time_point currentTime = std::chrono::system_clock::now();
|
||||
std::chrono::milliseconds interval(oneIterationInterval);
|
||||
std::chrono::milliseconds timeSinceStart = std::chrono::duration_cast<std::chrono::milliseconds>(currentTime - transportStartDate);
|
||||
int64 intervalsPassed = timeSinceStart.count() / oneIterationInterval;
|
||||
std::chrono::system_clock::time_point nextDeparture = transportStartDate + (interval * (intervalsPassed + 1));
|
||||
return nextDeparture;
|
||||
}
|
||||
}
|
||||
|
||||
MotionTransport::MotionTransport() : Transport(), _transportInfo(nullptr), _isMoving(true), _pendingStop(false), _triggeredArrivalEvent(false), _triggeredDepartureEvent(false), _passengersLoaded(false), _delayedTeleport(false),
|
||||
_requiresFirstDepartureSync(false), _firstDepartureTime(transportStartDate)
|
||||
{
|
||||
m_updateFlag = UPDATEFLAG_TRANSPORT | UPDATEFLAG_LOWGUID | UPDATEFLAG_STATIONARY_POSITION | UPDATEFLAG_ROTATION;
|
||||
}
|
||||
@@ -76,6 +97,13 @@ bool MotionTransport::CreateMoTrans(ObjectGuid::LowType guidlow, uint32 entry, u
|
||||
|
||||
_transportInfo = tInfo;
|
||||
|
||||
// Enable transport sync only in cluster mode and for transport with several maps.
|
||||
if (sConfigMgr->GetOption<bool>("Cluster.Enabled", false) && tInfo->mapsUsed.size() > 1)
|
||||
{
|
||||
_requiresFirstDepartureSync = true;
|
||||
this->SetPhaseMask(2, true);
|
||||
}
|
||||
|
||||
// initialize waypoints
|
||||
_nextFrame = tInfo->keyFrames.begin();
|
||||
_currentFrame = _nextFrame++;
|
||||
@@ -108,6 +136,56 @@ bool MotionTransport::CreateMoTrans(ObjectGuid::LowType guidlow, uint32 entry, u
|
||||
return true;
|
||||
}
|
||||
|
||||
// Delays first departure to make sure that transport follows strict schedule
|
||||
// and with that makes transport synced between cluster nodes.
|
||||
uint32 MotionTransport::HandleFirstDepartureSync(uint32 diff)
|
||||
{
|
||||
if (!_requiresFirstDepartureSync)
|
||||
return diff;
|
||||
|
||||
if (_firstDepartureTime == transportStartDate)
|
||||
{
|
||||
_firstDepartureTime = calculateNextDepartureTime(_transportInfo->pathTime);
|
||||
return diff;
|
||||
}
|
||||
|
||||
// Making system call for current time to be more accurate.
|
||||
// Shouldn't be an issue since it runs only before the very first departure.
|
||||
int32 millLeftToDeparture = std::chrono::duration_cast<std::chrono::milliseconds>(_firstDepartureTime - std::chrono::system_clock::now()).count();
|
||||
if (millLeftToDeparture > 0)
|
||||
return diff;
|
||||
|
||||
// At this point we are ready for first departure.
|
||||
_requiresFirstDepartureSync = false;
|
||||
this->SetPhaseMask(1, true);
|
||||
|
||||
// Players don't know about transport because it was in a different phase.
|
||||
// We need to notify players that object exists before departure.
|
||||
Map::PlayerList const& players = this->GetMap()->GetPlayers();
|
||||
if (!players.IsEmpty())
|
||||
{
|
||||
for (Map::PlayerList::const_iterator i = players.begin(); i != players.end(); ++i)
|
||||
{
|
||||
if (Player* player = i->GetSource())
|
||||
{
|
||||
// Same phase filter as Map::SendInitTransports: players outside
|
||||
// the transport's phase must not get a create-block for it.
|
||||
if (!player->InSamePhase(this))
|
||||
continue;
|
||||
|
||||
UpdateData transData;
|
||||
this->BuildCreateUpdateBlockForPlayer(&transData, player);
|
||||
|
||||
WorldPacket packet;
|
||||
transData.BuildPacket(packet);
|
||||
player->SendDirectMessage(&packet);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return millLeftToDeparture * -1;
|
||||
}
|
||||
|
||||
void MotionTransport::CleanupsBeforeDelete(bool finalCleanup /*= true*/)
|
||||
{
|
||||
UnloadStaticPassengers();
|
||||
@@ -137,6 +215,13 @@ void MotionTransport::BuildUpdate(UpdateDataMapType& data_map)
|
||||
|
||||
void MotionTransport::Update(uint32 diff)
|
||||
{
|
||||
if (_requiresFirstDepartureSync)
|
||||
{
|
||||
diff = HandleFirstDepartureSync(diff);
|
||||
if (_requiresFirstDepartureSync)
|
||||
return;
|
||||
}
|
||||
|
||||
uint32 const positionUpdateDelay = 1;
|
||||
|
||||
if (AI())
|
||||
|
||||
@@ -90,6 +90,8 @@ private:
|
||||
void UpdatePassengerPositions(PassengerSet& passengers);
|
||||
void DoEventIfAny(KeyFrame const& node, bool departure);
|
||||
|
||||
uint32 HandleFirstDepartureSync(uint32 diff);
|
||||
|
||||
//! Helpers to know if stop frame was reached
|
||||
bool IsMoving() const { return _isMoving; }
|
||||
void SetMoving(bool val) { _isMoving = val; }
|
||||
@@ -109,6 +111,9 @@ private:
|
||||
mutable std::mutex Lock;
|
||||
bool _passengersLoaded;
|
||||
bool _delayedTeleport;
|
||||
|
||||
bool _requiresFirstDepartureSync;
|
||||
std::chrono::system_clock::time_point _firstDepartureTime;
|
||||
};
|
||||
|
||||
class StaticTransport : public Transport
|
||||
|
||||
@@ -4181,8 +4181,11 @@ void Unit::SetCurrentCastedSpell(Spell* pSpell)
|
||||
}
|
||||
case CURRENT_CHANNELED_SPELL:
|
||||
{
|
||||
// channel spells always break generic non-delayed and any channeled spells
|
||||
InterruptSpell(CURRENT_GENERIC_SPELL, false);
|
||||
// channel spells always break generic non-delayed and any channeled spells,
|
||||
// unless the channel itself is allowed to run alongside other actions
|
||||
if (!pSpell->GetSpellInfo()->IsActionAllowedChannel())
|
||||
InterruptSpell(CURRENT_GENERIC_SPELL, false);
|
||||
|
||||
InterruptSpell(CURRENT_CHANNELED_SPELL, true, true, bySelf);
|
||||
|
||||
// it also does break autorepeat if not Auto Shot
|
||||
|
||||
@@ -34,6 +34,7 @@
|
||||
#include "Player.h"
|
||||
#include "ScriptMgr.h"
|
||||
#include "SharedDefines.h"
|
||||
#include "TC9Sidecar.h"
|
||||
#include "UpdateFieldFlags.h"
|
||||
#include "Util.h"
|
||||
#include "World.h"
|
||||
@@ -329,7 +330,7 @@ void Group::ConvertToRaid()
|
||||
|
||||
_initRaidSubGroupsCounter();
|
||||
|
||||
if (!isBGGroup() && !isBFGroup())
|
||||
if (!sToCloud9Sidecar->ClusterModeEnabled() && !isBGGroup() && !isBFGroup())
|
||||
{
|
||||
CharacterDatabasePreparedStatement* stmt = CharacterDatabase.GetPreparedStatement(CHAR_UPD_GROUP_TYPE);
|
||||
|
||||
@@ -584,6 +585,52 @@ bool Group::AddMember(Player* player, uint8 roles /* = 0 */)
|
||||
return true;
|
||||
}
|
||||
|
||||
void Group::AddMemberWithGuid(ObjectGuid guid)
|
||||
{
|
||||
// Idempotent under sidecar event redelivery: never duplicate a MemberSlot.
|
||||
if (IsMember(guid))
|
||||
return;
|
||||
|
||||
if (Player* player = ObjectAccessor::FindPlayer(guid))
|
||||
{
|
||||
AddMember(player);
|
||||
return;
|
||||
}
|
||||
|
||||
// Get first not-full group
|
||||
uint8 subGroup = 0;
|
||||
if (m_subGroupsCounts)
|
||||
{
|
||||
bool groupFound = false;
|
||||
for (; subGroup < MAX_RAID_SUBGROUPS; ++subGroup)
|
||||
{
|
||||
if (m_subGroupsCounts[subGroup] < MAXGROUPSIZE)
|
||||
{
|
||||
groupFound = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
// We are raid group and no one slot is free
|
||||
if (!groupFound)
|
||||
return;
|
||||
}
|
||||
|
||||
MemberSlot member;
|
||||
member.guid = guid;
|
||||
sCharacterCache->GetCharacterNameByGuid(guid, member.name);
|
||||
member.group = subGroup;
|
||||
member.flags = 0;
|
||||
member.roles = 0;
|
||||
m_memberSlots.push_back(member);
|
||||
|
||||
if (!isBGGroup() && !isBFGroup())
|
||||
{
|
||||
sCharacterCache->UpdateCharacterGroup(guid, GetGUID());
|
||||
}
|
||||
|
||||
SubGroupCounterIncrease(subGroup);
|
||||
}
|
||||
|
||||
bool Group::RemoveMember(ObjectGuid guid, RemoveMethod const& method /*= GROUP_REMOVEMETHOD_DEFAULT*/, ObjectGuid kicker /*= ObjectGuid::Empty*/, char const* reason /*= nullptr*/)
|
||||
{
|
||||
BroadcastGroupUpdate();
|
||||
@@ -595,8 +642,14 @@ bool Group::RemoveMember(ObjectGuid guid, RemoveMethod const& method /*= GROUP_R
|
||||
return m_memberSlots.size() > 0;
|
||||
}
|
||||
|
||||
// remove member and change leader (if need) only if strong more 2 members _before_ member remove (BG/BF allow 1 member group)
|
||||
if (GetMembersCount() > ((isBGGroup() || isLFGGroup() || isBFGroup()) ? 1u : 2u))
|
||||
// remove member and change leader (if need) only if strong more 2 members _before_ member remove (BG/BF allow 1 member group),
|
||||
// except in cluster mode, where this branch is taken for any size: the group
|
||||
// service owns the group lifecycle and local Disband() is a no-op, so always
|
||||
// unlink the removed member here; waiting for the disband event leaves the
|
||||
// last two members with a dangling group pointer whenever that event is lost
|
||||
// (group service restart).
|
||||
if (GetMembersCount() > ((isBGGroup() || isLFGGroup() || isBFGroup()) ? 1u : 2u)
|
||||
|| (sToCloud9Sidecar->ClusterModeEnabled() && !isBGGroup() && !isBFGroup()))
|
||||
{
|
||||
Player* player = ObjectAccessor::FindConnectedPlayer(guid);
|
||||
if (player)
|
||||
@@ -616,19 +669,24 @@ bool Group::RemoveMember(ObjectGuid guid, RemoveMethod const& method /*= GROUP_R
|
||||
player->UpdateForQuestWorldObjects();
|
||||
}
|
||||
|
||||
WorldPacket data;
|
||||
|
||||
if (method == GROUP_REMOVEMETHOD_KICK || method == GROUP_REMOVEMETHOD_KICK_LFG)
|
||||
// BG/BF groups stay locally owned in cluster mode (see the gates above),
|
||||
// so their removal packets must not be delegated to the group service.
|
||||
if (!sToCloud9Sidecar->ClusterModeEnabled() || isBGGroup() || isBFGroup())
|
||||
{
|
||||
data.Initialize(SMSG_GROUP_UNINVITE, 0);
|
||||
player->SendDirectMessage(&data);
|
||||
}
|
||||
WorldPacket data;
|
||||
|
||||
// Do we really need to send this opcode?
|
||||
data.Initialize(SMSG_GROUP_LIST, 1 + 1 + 1 + 1 + 8 + 4 + 4 + 8);
|
||||
data << uint8(0x10) << uint8(0) << uint8(0) << uint8(0);
|
||||
data << m_guid << uint32(m_counter) << uint32(0) << uint64(0);
|
||||
player->SendDirectMessage(&data);
|
||||
if (method == GROUP_REMOVEMETHOD_KICK || method == GROUP_REMOVEMETHOD_KICK_LFG)
|
||||
{
|
||||
data.Initialize(SMSG_GROUP_UNINVITE, 0);
|
||||
player->GetSession()->SendPacket(&data);
|
||||
}
|
||||
|
||||
// Do we really need to send this opcode?
|
||||
data.Initialize(SMSG_GROUP_LIST, 1 + 1 + 1 + 1 + 8 + 4 + 4 + 8);
|
||||
data << uint8(0x10) << uint8(0) << uint8(0) << uint8(0);
|
||||
data << m_guid << uint32(m_counter) << uint32(0) << uint64(0);
|
||||
player->GetSession()->SendPacket(&data);
|
||||
}
|
||||
}
|
||||
|
||||
// Remove player from group in DB
|
||||
@@ -763,7 +821,7 @@ void Group::ChangeLeader(ObjectGuid newLeaderGuid)
|
||||
sScriptMgr->OnGroupChangeLeader(this, newLeaderGuid, m_leaderGuid); // This hook should be executed at the end - Not used anywhere in the original core
|
||||
}
|
||||
|
||||
void Group::Disband(bool hideDestroy /* = false */)
|
||||
void Group::ForcedDisband(bool hideDestroy /* = false */)
|
||||
{
|
||||
sScriptMgr->OnGroupDisband(this);
|
||||
|
||||
@@ -859,6 +917,14 @@ void Group::Disband(bool hideDestroy /* = false */)
|
||||
delete this;
|
||||
}
|
||||
|
||||
void Group::Disband(bool hideDestroy /* = false */)
|
||||
{
|
||||
if (sToCloud9Sidecar->ClusterModeEnabled() && !this->isBFGroup() && !this->isBGGroup())
|
||||
return;
|
||||
|
||||
ForcedDisband(hideDestroy);
|
||||
}
|
||||
|
||||
/*********************************************************/
|
||||
/*** LOOT SYSTEM ***/
|
||||
/*********************************************************/
|
||||
@@ -1798,6 +1864,12 @@ void Group::SendTargetIconList(WorldSession* session)
|
||||
|
||||
void Group::SendUpdate()
|
||||
{
|
||||
if (sToCloud9Sidecar->ClusterModeEnabled() && !this->isBFGroup() && !this->isBGGroup())
|
||||
{
|
||||
// Group service responsible for sending these updates.
|
||||
return;
|
||||
}
|
||||
|
||||
for (member_witerator witr = m_memberSlots.begin(); witr != m_memberSlots.end(); ++witr)
|
||||
SendUpdateToPlayer(witr->guid, &(*witr));
|
||||
}
|
||||
@@ -2214,7 +2286,7 @@ void Roll::targetObjectBuildLink()
|
||||
void Group::SetDungeonDifficulty(Difficulty difficulty)
|
||||
{
|
||||
m_dungeonDifficulty = difficulty;
|
||||
if (!isBGGroup() && !isBFGroup())
|
||||
if (!sToCloud9Sidecar->ClusterModeEnabled() && !isBGGroup() && !isBFGroup())
|
||||
{
|
||||
CharacterDatabasePreparedStatement* stmt = CharacterDatabase.GetPreparedStatement(CHAR_UPD_GROUP_DIFFICULTY);
|
||||
|
||||
@@ -2235,7 +2307,7 @@ void Group::SetDungeonDifficulty(Difficulty difficulty)
|
||||
void Group::SetRaidDifficulty(Difficulty difficulty)
|
||||
{
|
||||
m_raidDifficulty = difficulty;
|
||||
if (!isBGGroup() && !isBFGroup())
|
||||
if (!sToCloud9Sidecar->ClusterModeEnabled() && !isBGGroup() && !isBFGroup())
|
||||
{
|
||||
CharacterDatabasePreparedStatement* stmt = CharacterDatabase.GetPreparedStatement(CHAR_UPD_GROUP_RAID_DIFFICULTY);
|
||||
|
||||
|
||||
@@ -37,6 +37,7 @@ class Unit;
|
||||
class WorldObject;
|
||||
class WorldPacket;
|
||||
class WorldSession;
|
||||
class ToCloud9GroupHooks;
|
||||
|
||||
struct MapEntry;
|
||||
|
||||
@@ -174,6 +175,7 @@ public:
|
||||
/** todo: uninvite people that not accepted invite **/
|
||||
class Group
|
||||
{
|
||||
friend class ToCloud9GroupHooks;
|
||||
public:
|
||||
struct MemberSlot
|
||||
{
|
||||
@@ -349,6 +351,9 @@ protected:
|
||||
void SubGroupCounterDecrease(uint8 subgroup);
|
||||
void ToggleGroupMemberFlag(member_witerator slot, uint8 flag, bool apply);
|
||||
|
||||
void AddMemberWithGuid(ObjectGuid guid);
|
||||
void ForcedDisband(bool hideDestroy = false);
|
||||
|
||||
MemberSlotList m_memberSlots;
|
||||
GroupRefMgr m_memberMgr;
|
||||
InvitesList m_invitees;
|
||||
|
||||
@@ -53,7 +53,11 @@ void GroupMgr::InitGroupIds()
|
||||
|
||||
void GroupMgr::RegisterGroupId(ObjectGuid::LowType groupId)
|
||||
{
|
||||
// Allocation was done in InitGroupIds()
|
||||
// InitGroupIds() sizes the bitmap to the local MAX(guid) at startup. In cluster mode the
|
||||
// group service assigns ids (auto-increment), which can exceed that bound, so grow on demand.
|
||||
if (groupId >= _groupIds.size())
|
||||
_groupIds.resize(groupId + 1);
|
||||
|
||||
_groupIds[groupId] = true;
|
||||
|
||||
// Groups are pulled in ascending order from db and _nextGroupId is initialized with 1,
|
||||
|
||||
@@ -18,6 +18,8 @@
|
||||
#include "ChannelMgr.h"
|
||||
#include "ObjectMgr.h" // for normalizePlayerName
|
||||
#include "Player.h"
|
||||
#include "Language.h"
|
||||
#include "TC9Sidecar.h"
|
||||
#include <cctype>
|
||||
|
||||
void WorldSession::HandleJoinChannel(WorldPacket& recvPacket)
|
||||
@@ -38,6 +40,23 @@ void WorldSession::HandleJoinChannel(WorldPacket& recvPacket)
|
||||
AreaTableEntry const* zone = sAreaTableStore.LookupEntry(GetPlayer()->GetZoneId());
|
||||
if (!zone || !GetPlayer()->CanJoinConstantChannelInZone(channel, zone))
|
||||
return;
|
||||
|
||||
// Cluster mode rebuilds the localized channel name so nodes agree on it;
|
||||
// stock servers keep the client-supplied name.
|
||||
if (sToCloud9Sidecar->ClusterModeEnabled())
|
||||
{
|
||||
auto const locale = GetSessionDbcLocale();
|
||||
std::string const& zoneName = zone->area_name[locale];
|
||||
std::string const cityName = sObjectMgr->GetAcoreStringForDBCLocale(LANG_CHANNEL_CITY);
|
||||
char const* nameExt = (channel->flags & CHANNEL_DBC_FLAG_CITY_ONLY) ? cityName.c_str() : zoneName.c_str();
|
||||
|
||||
std::array<char, 128> buffer{};
|
||||
if (char const* pattern = channel->pattern[locale])
|
||||
{
|
||||
std::snprintf(buffer.data(), buffer.size(), pattern, nameExt);
|
||||
channelName = buffer.data();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (channelName.empty())
|
||||
|
||||
@@ -54,6 +54,7 @@
|
||||
#include "SpellAuraEffects.h"
|
||||
#include "SpellAuras.h"
|
||||
#include "StringConvert.h"
|
||||
#include "TC9Sidecar.h"
|
||||
#include "Tokenize.h"
|
||||
#include "Transport.h"
|
||||
#include "Util.h"
|
||||
@@ -71,125 +72,126 @@ bool LoginQueryHolder::Initialize()
|
||||
SetSize(MAX_PLAYER_LOGIN_QUERY);
|
||||
|
||||
bool res = true;
|
||||
ObjectGuid::LowType lowGuid = m_guid.GetCounter();
|
||||
uint64 rawGUID = m_guid.GetRawValue();
|
||||
|
||||
CharacterDatabasePreparedStatement* stmt = CharacterDatabase.GetPreparedStatement(CHAR_SEL_CHARACTER);
|
||||
stmt->SetData(0, lowGuid);
|
||||
stmt->SetData(0, rawGUID);
|
||||
res &= SetPreparedQuery(PLAYER_LOGIN_QUERY_LOAD_FROM, stmt);
|
||||
|
||||
stmt = CharacterDatabase.GetPreparedStatement(CHAR_SEL_CHARACTER_AURAS);
|
||||
stmt->SetData(0, lowGuid);
|
||||
stmt->SetData(0, rawGUID);
|
||||
res &= SetPreparedQuery(PLAYER_LOGIN_QUERY_LOAD_AURAS, stmt);
|
||||
|
||||
stmt = CharacterDatabase.GetPreparedStatement(CHAR_SEL_CHARACTER_SPELL);
|
||||
stmt->SetData(0, lowGuid);
|
||||
stmt->SetData(0, rawGUID);
|
||||
res &= SetPreparedQuery(PLAYER_LOGIN_QUERY_LOAD_SPELLS, stmt);
|
||||
|
||||
stmt = CharacterDatabase.GetPreparedStatement(CHAR_SEL_CHARACTER_QUESTSTATUS);
|
||||
stmt->SetData(0, lowGuid);
|
||||
stmt->SetData(0, rawGUID);
|
||||
res &= SetPreparedQuery(PLAYER_LOGIN_QUERY_LOAD_QUEST_STATUS, stmt);
|
||||
|
||||
stmt = CharacterDatabase.GetPreparedStatement(CHAR_SEL_CHARACTER_DAILYQUESTSTATUS);
|
||||
stmt->SetData(0, lowGuid);
|
||||
stmt->SetData(0, rawGUID);
|
||||
res &= SetPreparedQuery(PLAYER_LOGIN_QUERY_LOAD_DAILY_QUEST_STATUS, stmt);
|
||||
|
||||
stmt = CharacterDatabase.GetPreparedStatement(CHAR_SEL_CHARACTER_WEEKLYQUESTSTATUS);
|
||||
stmt->SetData(0, lowGuid);
|
||||
stmt->SetData(0, rawGUID);
|
||||
res &= SetPreparedQuery(PLAYER_LOGIN_QUERY_LOAD_WEEKLY_QUEST_STATUS, stmt);
|
||||
|
||||
stmt = CharacterDatabase.GetPreparedStatement(CHAR_SEL_CHARACTER_MONTHLYQUESTSTATUS);
|
||||
stmt->SetData(0, lowGuid);
|
||||
stmt->SetData(0, rawGUID);
|
||||
res &= SetPreparedQuery(PLAYER_LOGIN_QUERY_LOAD_MONTHLY_QUEST_STATUS, stmt);
|
||||
|
||||
stmt = CharacterDatabase.GetPreparedStatement(CHAR_SEL_CHARACTER_SEASONALQUESTSTATUS);
|
||||
stmt->SetData(0, lowGuid);
|
||||
stmt->SetData(0, rawGUID);
|
||||
res &= SetPreparedQuery(PLAYER_LOGIN_QUERY_LOAD_SEASONAL_QUEST_STATUS, stmt);
|
||||
|
||||
stmt = CharacterDatabase.GetPreparedStatement(CHAR_SEL_CHARACTER_REPUTATION);
|
||||
stmt->SetData(0, lowGuid);
|
||||
stmt->SetData(0, rawGUID);
|
||||
res &= SetPreparedQuery(PLAYER_LOGIN_QUERY_LOAD_REPUTATION, stmt);
|
||||
|
||||
stmt = CharacterDatabase.GetPreparedStatement(CHAR_SEL_CHARACTER_INVENTORY);
|
||||
stmt->SetData(0, lowGuid);
|
||||
stmt->SetData(0, rawGUID);
|
||||
res &= SetPreparedQuery(PLAYER_LOGIN_QUERY_LOAD_INVENTORY, stmt);
|
||||
|
||||
stmt = CharacterDatabase.GetPreparedStatement(CHAR_SEL_CHARACTER_ACTIONS);
|
||||
stmt->SetData(0, lowGuid);
|
||||
stmt->SetData(0, rawGUID);
|
||||
res &= SetPreparedQuery(PLAYER_LOGIN_QUERY_LOAD_ACTIONS, stmt);
|
||||
|
||||
stmt = CharacterDatabase.GetPreparedStatement(CHAR_SEL_MAIL);
|
||||
stmt->SetData(0, lowGuid);
|
||||
stmt->SetData(0, rawGUID);
|
||||
|
||||
res &= SetPreparedQuery(PLAYER_LOGIN_QUERY_LOAD_MAILS, stmt);
|
||||
|
||||
stmt = CharacterDatabase.GetPreparedStatement(CHAR_SEL_MAILITEMS);
|
||||
stmt->SetData(0, lowGuid);
|
||||
stmt->SetData(0, rawGUID);
|
||||
res &= SetPreparedQuery(PLAYER_LOGIN_QUERY_LOAD_MAIL_ITEMS, stmt);
|
||||
|
||||
stmt = CharacterDatabase.GetPreparedStatement(CHAR_SEL_CHARACTER_SOCIALLIST);
|
||||
stmt->SetData(0, lowGuid);
|
||||
stmt->SetData(0, rawGUID);
|
||||
res &= SetPreparedQuery(PLAYER_LOGIN_QUERY_LOAD_SOCIAL_LIST, stmt);
|
||||
|
||||
stmt = CharacterDatabase.GetPreparedStatement(CHAR_SEL_CHARACTER_HOMEBIND);
|
||||
stmt->SetData(0, lowGuid);
|
||||
stmt->SetData(0, rawGUID);
|
||||
res &= SetPreparedQuery(PLAYER_LOGIN_QUERY_LOAD_HOME_BIND, stmt);
|
||||
|
||||
stmt = CharacterDatabase.GetPreparedStatement(CHAR_SEL_CHARACTER_SPELLCOOLDOWNS);
|
||||
stmt->SetData(0, lowGuid);
|
||||
stmt->SetData(0, rawGUID);
|
||||
res &= SetPreparedQuery(PLAYER_LOGIN_QUERY_LOAD_SPELL_COOLDOWNS, stmt);
|
||||
|
||||
if (sWorld->getBoolConfig(CONFIG_DECLINED_NAMES_USED))
|
||||
{
|
||||
stmt = CharacterDatabase.GetPreparedStatement(CHAR_SEL_CHARACTER_DECLINEDNAMES);
|
||||
stmt->SetData(0, lowGuid);
|
||||
stmt->SetData(0, rawGUID);
|
||||
res &= SetPreparedQuery(PLAYER_LOGIN_QUERY_LOAD_DECLINED_NAMES, stmt);
|
||||
}
|
||||
|
||||
stmt = CharacterDatabase.GetPreparedStatement(CHAR_SEL_CHARACTER_ACHIEVEMENTS);
|
||||
stmt->SetData(0, lowGuid);
|
||||
stmt->SetData(0, rawGUID);
|
||||
res &= SetPreparedQuery(PLAYER_LOGIN_QUERY_LOAD_ACHIEVEMENTS, stmt);
|
||||
|
||||
stmt = CharacterDatabase.GetPreparedStatement(CHAR_SEL_CHARACTER_CRITERIAPROGRESS);
|
||||
stmt->SetData(0, lowGuid);
|
||||
stmt->SetData(0, rawGUID);
|
||||
res &= SetPreparedQuery(PLAYER_LOGIN_QUERY_LOAD_CRITERIA_PROGRESS, stmt);
|
||||
|
||||
stmt = CharacterDatabase.GetPreparedStatement(CHAR_SEL_CHARACTER_EQUIPMENTSETS);
|
||||
stmt->SetData(0, lowGuid);
|
||||
stmt->SetData(0, rawGUID);
|
||||
res &= SetPreparedQuery(PLAYER_LOGIN_QUERY_LOAD_EQUIPMENT_SETS, stmt);
|
||||
|
||||
stmt = CharacterDatabase.GetPreparedStatement(CHAR_SEL_CHARACTER_ENTRY_POINT);
|
||||
stmt->SetData(0, lowGuid);
|
||||
stmt->SetData(0, rawGUID);
|
||||
res &= SetPreparedQuery(PLAYER_LOGIN_QUERY_LOAD_ENTRY_POINT, stmt);
|
||||
|
||||
stmt = CharacterDatabase.GetPreparedStatement(CHAR_SEL_CHARACTER_GLYPHS);
|
||||
stmt->SetData(0, lowGuid);
|
||||
stmt->SetData(0, rawGUID);
|
||||
res &= SetPreparedQuery(PLAYER_LOGIN_QUERY_LOAD_GLYPHS, stmt);
|
||||
|
||||
stmt = CharacterDatabase.GetPreparedStatement(CHAR_SEL_CHARACTER_TALENTS);
|
||||
stmt->SetData(0, lowGuid);
|
||||
stmt->SetData(0, rawGUID);
|
||||
res &= SetPreparedQuery(PLAYER_LOGIN_QUERY_LOAD_TALENTS, stmt);
|
||||
|
||||
stmt = CharacterDatabase.GetPreparedStatement(CHAR_SEL_PLAYER_ACCOUNT_DATA);
|
||||
stmt->SetData(0, lowGuid);
|
||||
stmt->SetData(0, rawGUID);
|
||||
res &= SetPreparedQuery(PLAYER_LOGIN_QUERY_LOAD_ACCOUNT_DATA, stmt);
|
||||
|
||||
stmt = CharacterDatabase.GetPreparedStatement(CHAR_SEL_CHARACTER_SKILLS);
|
||||
stmt->SetData(0, lowGuid);
|
||||
stmt->SetData(0, rawGUID);
|
||||
res &= SetPreparedQuery(PLAYER_LOGIN_QUERY_LOAD_SKILLS, stmt);
|
||||
|
||||
stmt = CharacterDatabase.GetPreparedStatement(CHAR_SEL_CHARACTER_RANDOMBG);
|
||||
stmt->SetData(0, lowGuid);
|
||||
stmt->SetData(0, rawGUID);
|
||||
res &= SetPreparedQuery(PLAYER_LOGIN_QUERY_LOAD_RANDOM_BG, stmt);
|
||||
|
||||
stmt = CharacterDatabase.GetPreparedStatement(CHAR_SEL_CHARACTER_BANNED);
|
||||
stmt->SetData(0, lowGuid);
|
||||
stmt->SetData(0, rawGUID);
|
||||
res &= SetPreparedQuery(PLAYER_LOGIN_QUERY_LOAD_BANNED, stmt);
|
||||
|
||||
stmt = CharacterDatabase.GetPreparedStatement(CHAR_SEL_CHARACTER_QUESTSTATUSREW);
|
||||
stmt->SetData(0, lowGuid);
|
||||
stmt->SetData(0, rawGUID);
|
||||
res &= SetPreparedQuery(PLAYER_LOGIN_QUERY_LOAD_QUEST_STATUS_REW, stmt);
|
||||
|
||||
stmt = CharacterDatabase.GetPreparedStatement(CHAR_SEL_BREW_OF_THE_MONTH);
|
||||
stmt->SetData(0, lowGuid);
|
||||
stmt->SetData(0, rawGUID);
|
||||
res &= SetPreparedQuery(PLAYER_LOGIN_QUERY_LOAD_BREW_OF_THE_MONTH, stmt);
|
||||
|
||||
stmt = CharacterDatabase.GetPreparedStatement(CHAR_SEL_ACCOUNT_INSTANCELOCKTIMES);
|
||||
@@ -197,19 +199,19 @@ bool LoginQueryHolder::Initialize()
|
||||
res &= SetPreparedQuery(PLAYER_LOGIN_QUERY_LOAD_INSTANCE_LOCK_TIMES, stmt);
|
||||
|
||||
stmt = CharacterDatabase.GetPreparedStatement(CHAR_SEL_CORPSE_LOCATION);
|
||||
stmt->SetData(0, lowGuid);
|
||||
stmt->SetData(0, rawGUID);
|
||||
res &= SetPreparedQuery(PLAYER_LOGIN_QUERY_LOAD_CORPSE_LOCATION, stmt);
|
||||
|
||||
stmt = CharacterDatabase.GetPreparedStatement(CHAR_SEL_CHAR_SETTINGS);
|
||||
stmt->SetData(0, lowGuid);
|
||||
stmt->SetData(0, rawGUID);
|
||||
res &= SetPreparedQuery(PLAYER_LOGIN_QUERY_LOAD_CHARACTER_SETTINGS, stmt);
|
||||
|
||||
stmt = CharacterDatabase.GetPreparedStatement(CHAR_SEL_CHAR_PETS);
|
||||
stmt->SetData(0, lowGuid);
|
||||
stmt->SetData(0, rawGUID);
|
||||
res &= SetPreparedQuery(PLAYER_LOGIN_QUERY_LOAD_PET_SLOTS, stmt);
|
||||
|
||||
stmt = CharacterDatabase.GetPreparedStatement(CHAR_SEL_CHAR_ACHIEVEMENT_OFFLINE_UPDATES);
|
||||
stmt->SetData(0, lowGuid);
|
||||
stmt->SetData(0, rawGUID);
|
||||
res &= SetPreparedQuery(PLAYER_LOGIN_QUERY_LOAD_OFFLINE_ACHIEVEMENTS_UPDATES, stmt);
|
||||
|
||||
return res;
|
||||
@@ -698,7 +700,9 @@ void WorldSession::HandlePlayerLoginOpcode(WorldPacket& recvData)
|
||||
ObjectGuid playerGuid;
|
||||
recvData >> playerGuid;
|
||||
|
||||
if (!IsLegitCharacterForAccount(playerGuid))
|
||||
// The ownership check is delegated to the gateway in cluster mode, but a
|
||||
// non-player GUID is invalid regardless of who authenticated the session.
|
||||
if (!playerGuid.IsPlayer() || (!sToCloud9Sidecar->ClusterModeEnabled() && !IsLegitCharacterForAccount(playerGuid)))
|
||||
{
|
||||
LOG_ERROR("network", "Account ({}) can't login with that character ({}).", GetAccountId(), playerGuid.ToString());
|
||||
KickPlayer("Account can't login with this character");
|
||||
@@ -847,20 +851,28 @@ void WorldSession::HandlePlayerLoginFromDB(LoginQueryHolder const& holder)
|
||||
chH.PSendSysMessage("{}", GitRevision::GetFullVersion());
|
||||
}
|
||||
|
||||
if (uint32 guildId = sCharacterCache->GetCharacterGuildIdByGuid(pCurrChar->GetGUID()))
|
||||
if (!sToCloud9Sidecar->ClusterModeEnabled())
|
||||
{
|
||||
Guild* guild = sGuildMgr->GetGuildById(guildId);
|
||||
Guild::Member const* member = guild ? guild->GetMember(pCurrChar->GetGUID()) : nullptr;
|
||||
if (member)
|
||||
if (uint32 guildId = sCharacterCache->GetCharacterGuildIdByGuid(pCurrChar->GetGUID()))
|
||||
{
|
||||
pCurrChar->SetInGuild(guildId);
|
||||
pCurrChar->SetRank(member->GetRankId());
|
||||
guild->SendLoginInfo(this);
|
||||
Guild* guild = sGuildMgr->GetGuildById(guildId);
|
||||
Guild::Member const* member = guild ? guild->GetMember(pCurrChar->GetGUID()) : nullptr;
|
||||
if (member)
|
||||
{
|
||||
pCurrChar->SetInGuild(guildId);
|
||||
pCurrChar->SetRank(member->GetRankId());
|
||||
guild->SendLoginInfo(this);
|
||||
}
|
||||
else
|
||||
{
|
||||
LOG_ERROR("network.opcode", "Player {} ({}) marked as member of not existing guild (id: {}), removing guild membership for player.",
|
||||
pCurrChar->GetName(), pCurrChar->GetGUID().ToString(), guildId);
|
||||
pCurrChar->SetInGuild(0);
|
||||
pCurrChar->SetRank(0);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
LOG_ERROR("network.opcode", "Player {} ({}) marked as member of not existing guild (id: {}), removing guild membership for player.",
|
||||
pCurrChar->GetName(), pCurrChar->GetGUID().ToString(), guildId);
|
||||
pCurrChar->SetInGuild(0);
|
||||
pCurrChar->SetRank(0);
|
||||
}
|
||||
@@ -914,9 +926,12 @@ void WorldSession::HandlePlayerLoginFromDB(LoginQueryHolder const& holder)
|
||||
|
||||
pCurrChar->SendInitialPacketsAfterAddToMap();
|
||||
|
||||
CharacterDatabasePreparedStatement* stmt = CharacterDatabase.GetPreparedStatement(CHAR_UPD_CHAR_ONLINE);
|
||||
stmt->SetData(0, pCurrChar->GetGUID().GetCounter());
|
||||
CharacterDatabase.Execute(stmt);
|
||||
if (!sToCloud9Sidecar->ClusterModeEnabled())
|
||||
{
|
||||
CharacterDatabasePreparedStatement* stmt = CharacterDatabase.GetPreparedStatement(CHAR_UPD_CHAR_ONLINE);
|
||||
stmt->SetData(0, pCurrChar->GetGUID().GetCounter());
|
||||
CharacterDatabase.Execute(stmt);
|
||||
}
|
||||
|
||||
LoginDatabasePreparedStatement* loginStmt = LoginDatabase.GetPreparedStatement(LOGIN_UPD_ACCOUNT_ONLINE);
|
||||
loginStmt->SetData(0, GetAccountId());
|
||||
|
||||
@@ -716,6 +716,18 @@ namespace Acore
|
||||
|
||||
void WorldSession::HandleTextEmoteOpcode(WorldPacket& recvData)
|
||||
{
|
||||
uint32 text_emote;
|
||||
recvData >> text_emote;
|
||||
|
||||
constexpr uint32 readyEmote = 126;
|
||||
|
||||
// Handle confirmation redirect when the player types "/ready" after the new node becomes available for redirection.
|
||||
if (text_emote == readyEmote && GetPlayer()->GetMap()->IsPlayerRedirectKickTimerActive())
|
||||
{
|
||||
HandleTC9PrepareForRedirect(recvData);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!GetPlayer()->IsAlive())
|
||||
return;
|
||||
|
||||
@@ -731,10 +743,9 @@ void WorldSession::HandleTextEmoteOpcode(WorldPacket& recvData)
|
||||
if (GetPlayer()->IsSpectator())
|
||||
return;
|
||||
|
||||
uint32 text_emote, emoteNum;
|
||||
uint32 emoteNum;
|
||||
ObjectGuid guid;
|
||||
|
||||
recvData >> text_emote;
|
||||
recvData >> emoteNum;
|
||||
recvData >> guid;
|
||||
|
||||
|
||||
@@ -22,7 +22,6 @@
|
||||
#include "CellImpl.h"
|
||||
#include "Chat.h"
|
||||
#include "Corpse.h"
|
||||
#include "GameGraveyard.h"
|
||||
#include "GameTime.h"
|
||||
#include "InstanceSaveMgr.h"
|
||||
#include "Log.h"
|
||||
@@ -520,15 +519,10 @@ void WorldSession::HandleMoverRelocation(MovementInfo& movementInfo, Unit* mover
|
||||
if (plrMover->IsAlive())
|
||||
plrMover->KillPlayer();
|
||||
}
|
||||
else if (!plrMover->HasPlayerFlag(PLAYER_FLAGS_IS_OUT_OF_BOUNDS))
|
||||
{
|
||||
GraveyardStruct const* grave = sGraveyard->GetClosestGraveyard(plrMover, plrMover->GetTeamId());
|
||||
if (grave)
|
||||
{
|
||||
plrMover->TeleportTo(grave->Map, grave->x, grave->y, grave->z, plrMover->GetOrientation());
|
||||
plrMover->Relocate(grave->x, grave->y, grave->z, plrMover->GetOrientation());
|
||||
}
|
||||
}
|
||||
// Rescue only released ghosts: teleporting an unreleased body would move the corpse
|
||||
// out of instances (e.g. Eye of Eternity platform destruction, issue #25757).
|
||||
else if (plrMover->HasPlayerFlag(PLAYER_FLAGS_GHOST) && !plrMover->HasPlayerFlag(PLAYER_FLAGS_IS_OUT_OF_BOUNDS))
|
||||
plrMover->RepopAtGraveyard();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -27,6 +27,7 @@
|
||||
#include "SocialMgr.h"
|
||||
#include "Spell.h"
|
||||
#include "SpellMgr.h"
|
||||
#include "TC9Sidecar.h"
|
||||
#include "World.h"
|
||||
#include "WorldPacket.h"
|
||||
#include "WorldSession.h"
|
||||
@@ -505,6 +506,79 @@ void WorldSession::HandleAcceptTradeOpcode(WorldPacket& /*recvPacket*/)
|
||||
}
|
||||
}
|
||||
|
||||
bool needsCrossrealmHandling = sToCloud9Sidecar->IsCrossrealm() && _player->GetGUID().GetRealmID() != trader->GetGUID().GetRealmID();
|
||||
|
||||
// Create new items for crossrealm usage.
|
||||
if (needsCrossrealmHandling)
|
||||
{
|
||||
CharacterDatabasePreparedStatement* stmt = nullptr;
|
||||
CharacterDatabaseTransaction trans = CharacterDatabase.BeginTransaction();
|
||||
stmt = CharacterDatabase.GetPreparedStatement(CHAR_NO_OP_PROVIDE_REALM_CONTEXT);
|
||||
stmt->SetData(0, _player->GetGUID().GetRealmID());
|
||||
trans->Append(stmt);
|
||||
|
||||
for (uint8 i = 0; i < TRADE_SLOT_TRADED_COUNT; i++)
|
||||
{
|
||||
if (myItems[i])
|
||||
{
|
||||
Item* newItem = myItems[i]->CloneItem(myItems[i]->GetCount(), trader);
|
||||
if (!newItem)
|
||||
{
|
||||
ItemPosCountVec playerDst;
|
||||
if (_player->CanStoreItem(NULL_BAG, NULL_SLOT, playerDst, myItems[i], false) == EQUIP_ERR_OK)
|
||||
_player->MoveItemToInventory(playerDst, myItems[i], true, true);
|
||||
|
||||
// Should we handle else statement here?
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
stmt = CharacterDatabase.GetPreparedStatement(CHAR_DEL_ITEM_INSTANCE);
|
||||
stmt->SetData(0, myItems[i]->GetGUID().GetCounter());
|
||||
trans->Append(stmt);
|
||||
|
||||
delete myItems[i];
|
||||
|
||||
myItems[i] = newItem;
|
||||
}
|
||||
}
|
||||
CharacterDatabase.CommitTransaction(trans);
|
||||
|
||||
trans = CharacterDatabase.BeginTransaction();
|
||||
|
||||
stmt = CharacterDatabase.GetPreparedStatement(CHAR_NO_OP_PROVIDE_REALM_CONTEXT);
|
||||
stmt->SetData(0, trader->GetGUID().GetRealmID());
|
||||
trans->Append(stmt);
|
||||
|
||||
for (uint8 i = 0; i < TRADE_SLOT_TRADED_COUNT; i++)
|
||||
{
|
||||
if (hisItems[i])
|
||||
{
|
||||
Item* newItem = hisItems[i]->CloneItem(hisItems[i]->GetCount(), _player);
|
||||
if (!newItem)
|
||||
{
|
||||
ItemPosCountVec playerDst;
|
||||
if (trader->CanStoreItem(NULL_BAG, NULL_SLOT, playerDst, hisItems[i], false) == EQUIP_ERR_OK)
|
||||
trader->MoveItemToInventory(playerDst, hisItems[i], true, true);
|
||||
|
||||
// Should we handle else statement here?
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
stmt = CharacterDatabase.GetPreparedStatement(CHAR_DEL_ITEM_INSTANCE);
|
||||
stmt->SetData(0, hisItems[i]->GetGUID().GetCounter());
|
||||
trans->Append(stmt);
|
||||
|
||||
delete hisItems[i];
|
||||
|
||||
hisItems[i] = newItem;
|
||||
}
|
||||
}
|
||||
CharacterDatabase.CommitTransaction(trans);
|
||||
|
||||
}
|
||||
|
||||
// execute trade: 2. store
|
||||
moveItems(myItems, hisItems);
|
||||
|
||||
@@ -575,11 +649,25 @@ void WorldSession::HandleAcceptTradeOpcode(WorldPacket& /*recvPacket*/)
|
||||
delete trader->m_trade;
|
||||
trader->m_trade = nullptr;
|
||||
|
||||
// desynchronized with the other saves here (SaveInventoryAndGoldToDB() not have own transaction guards)
|
||||
CharacterDatabaseTransaction trans = CharacterDatabase.BeginTransaction();
|
||||
_player->SaveInventoryAndGoldToDB(trans);
|
||||
trader->SaveInventoryAndGoldToDB(trans);
|
||||
CharacterDatabase.CommitTransaction(trans);
|
||||
// We can't use single transaction with different databases.
|
||||
if (needsCrossrealmHandling)
|
||||
{
|
||||
CharacterDatabaseTransaction trans = CharacterDatabase.BeginTransaction();
|
||||
_player->SaveInventoryAndGoldToDB(trans);
|
||||
CharacterDatabase.CommitTransaction(trans);
|
||||
|
||||
trans = CharacterDatabase.BeginTransaction();
|
||||
trader->SaveInventoryAndGoldToDB(trans);
|
||||
CharacterDatabase.CommitTransaction(trans);
|
||||
}
|
||||
else
|
||||
{
|
||||
// desynchronized with the other saves here (SaveInventoryAndGoldToDB() not have own transaction guards)
|
||||
CharacterDatabaseTransaction trans = CharacterDatabase.BeginTransaction();
|
||||
_player->SaveInventoryAndGoldToDB(trans);
|
||||
trader->SaveInventoryAndGoldToDB(trans);
|
||||
CharacterDatabase.CommitTransaction(trans);
|
||||
}
|
||||
|
||||
info.Status = TRADE_STATUS_TRADE_COMPLETE;
|
||||
trader->GetSession()->SendTradeStatus(info);
|
||||
|
||||
@@ -29,6 +29,7 @@
|
||||
#include "ObjectMgr.h"
|
||||
#include "Player.h"
|
||||
#include "ScriptMgr.h"
|
||||
#include "TC9Sidecar.h"
|
||||
#include "Timer.h"
|
||||
#include "Transport.h"
|
||||
#include "World.h"
|
||||
@@ -164,6 +165,9 @@ InstanceSave::~InstanceSave()
|
||||
|
||||
void InstanceSave::InsertToDB()
|
||||
{
|
||||
if (sToCloud9Sidecar->ClusterModeEnabled() && !sToCloud9Sidecar->IsMapAssigned(GetMapId()))
|
||||
return;
|
||||
|
||||
std::string data;
|
||||
uint32 completedEncounters = 0;
|
||||
|
||||
@@ -354,7 +358,7 @@ void InstanceSaveMgr::LoadResetTimes()
|
||||
{
|
||||
// assume that expired instances have already been cleaned
|
||||
// calculate the next reset time
|
||||
t = (t * DAY) / DAY;
|
||||
t = (t / DAY) * DAY;
|
||||
t += ((today - t) / period + 1) * period + diff;
|
||||
CharacterDatabase.DirectExecute("UPDATE instance_reset SET resettime = '{}' WHERE mapid = '{}' AND difficulty = '{}'", (uint32)t, mapid, difficulty);
|
||||
}
|
||||
@@ -454,6 +458,157 @@ void InstanceSaveMgr::LoadCharacterBinds()
|
||||
lock_instLists = false;
|
||||
}
|
||||
|
||||
// Runs on a std::async worker thread. Only the two synchronous CharacterDatabase
|
||||
// reads happen here (safe: the synch connection pool is mutex-guarded per connection).
|
||||
// All manager-state access is deferred to MergeWithNewInstanceSaves on the world thread.
|
||||
InstanceMapLoadRows InstanceSaveMgr::LoadInstanceSavesAndBindsForMapIDs(std::vector<uint32> const& mapIDs)
|
||||
{
|
||||
InstanceMapLoadRows rows;
|
||||
|
||||
std::stringstream mapIDsStr;
|
||||
for (size_t i = 0; i < mapIDs.size(); ++i)
|
||||
{
|
||||
mapIDsStr << mapIDs[i];
|
||||
if (i < mapIDs.size() - 1)
|
||||
mapIDsStr << ",";
|
||||
}
|
||||
|
||||
QueryResult result = CharacterDatabase.Query("SELECT id, map, resettime, difficulty, completedEncounters, data FROM instance WHERE map IN ({}) ORDER BY id ASC", mapIDsStr.str());
|
||||
if (result)
|
||||
{
|
||||
do
|
||||
{
|
||||
Field* fields = result->Fetch();
|
||||
|
||||
InstanceMapLoadRows::InstanceRow row;
|
||||
row.instanceId = fields[0].Get<uint32>();
|
||||
row.mapId = fields[1].Get<uint16>();
|
||||
row.resetTime = time_t(fields[2].Get<uint32>());
|
||||
row.difficulty = fields[3].Get<uint8>();
|
||||
row.completedEncounters = fields[4].Get<uint32>();
|
||||
row.data = fields[5].Get<std::string>();
|
||||
rows.instances.push_back(std::move(row));
|
||||
} while (result->NextRow());
|
||||
}
|
||||
|
||||
result = CharacterDatabase.Query("SELECT guid, instance, permanent, extended FROM character_instance WHERE instance IN (SELECT id FROM instance WHERE map IN ({}))", mapIDsStr.str());
|
||||
if (result)
|
||||
{
|
||||
do
|
||||
{
|
||||
Field* fields = result->Fetch();
|
||||
|
||||
InstanceMapLoadRows::BindRow row;
|
||||
row.guidLow = fields[0].Get<uint32>();
|
||||
row.instanceId = fields[1].Get<uint32>();
|
||||
row.perm = fields[2].Get<bool>();
|
||||
row.extended = fields[3].Get<bool>();
|
||||
rows.binds.push_back(row);
|
||||
} while (result->NextRow());
|
||||
}
|
||||
|
||||
return rows;
|
||||
}
|
||||
|
||||
// Runs on the world thread (TC9 async completion callback). Safe to touch
|
||||
// m_instanceSaveById, playerBindStorage, m_resetExtendedTimeByMapDifficulty and MapMgr here.
|
||||
void InstanceSaveMgr::MergeWithNewInstanceSaves(InstanceMapLoadRows const& loadResult)
|
||||
{
|
||||
// Same guard as LoadCharacterBinds: without it, an unbind cascading into
|
||||
// DeleteInstanceSaveIfNeeded would delete the very DB rows being merged.
|
||||
lock_instLists = true;
|
||||
|
||||
for (InstanceMapLoadRows::InstanceRow const& row : loadResult.instances)
|
||||
{
|
||||
MapEntry const* entry = sMapStore.LookupEntry(row.mapId);
|
||||
if (!entry)
|
||||
{
|
||||
LOG_ERROR("instance.save", "InstanceSaveMgr::MergeWithNewInstanceSaves: wrong mapid = {}, instanceid = {}!", row.mapId, row.instanceId);
|
||||
continue;
|
||||
}
|
||||
|
||||
// Same row validation as AddInstanceSave for rows loaded on reassignment.
|
||||
if (row.instanceId == 0)
|
||||
{
|
||||
LOG_ERROR("instance.save", "InstanceSaveMgr::MergeWithNewInstanceSaves: mapid = {}, wrong instanceid = {}!", row.mapId, row.instanceId);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (row.difficulty >= (entry->IsRaid() ? MAX_RAID_DIFFICULTY : MAX_DUNGEON_DIFFICULTY))
|
||||
{
|
||||
LOG_ERROR("instance.save", "InstanceSaveMgr::MergeWithNewInstanceSaves: mapid = {}, instanceid = {}, wrong difficulty {}!", row.mapId, row.instanceId, row.difficulty);
|
||||
continue;
|
||||
}
|
||||
|
||||
time_t extendedResetTime = 0;
|
||||
if (entry->IsRaid() || row.difficulty > DUNGEON_DIFFICULTY_NORMAL)
|
||||
extendedResetTime = GetExtendedResetTimeFor(row.mapId, Difficulty(row.difficulty));
|
||||
|
||||
InstanceSave* save = new InstanceSave(row.mapId, row.instanceId, Difficulty(row.difficulty), row.resetTime, extendedResetTime);
|
||||
save->SetCompletedEncounterMask(row.completedEncounters);
|
||||
save->SetInstanceData(row.data);
|
||||
if (row.resetTime > 0)
|
||||
save->SetResetTime(row.resetTime);
|
||||
|
||||
InstanceSaveHashMap::iterator currentSave = m_instanceSaveById.find(row.instanceId);
|
||||
if (currentSave != m_instanceSaveById.end())
|
||||
{
|
||||
InstanceSave* oldSave = currentSave->second;
|
||||
|
||||
// Unbind every player still pointing at the stale save before it is
|
||||
// freed, in-memory only: the DB rows were just refetched and are
|
||||
// rebound below. Iterate a copy, PlayerUnbindInstance mutates the list.
|
||||
GuidList players = oldSave->m_playerList;
|
||||
for (ObjectGuid const& playerGuid : players)
|
||||
PlayerUnbindInstance(playerGuid, oldSave->GetMapId(), oldSave->GetDifficulty(), false);
|
||||
|
||||
m_instanceSaveById.erase(currentSave);
|
||||
delete oldSave;
|
||||
}
|
||||
m_instanceSaveById[row.instanceId] = save;
|
||||
}
|
||||
|
||||
for (InstanceMapLoadRows::BindRow const& row : loadResult.binds)
|
||||
{
|
||||
InstanceSaveHashMap::iterator itr = m_instanceSaveById.find(row.instanceId);
|
||||
InstanceSave* save = itr != m_instanceSaveById.end() ? itr->second : nullptr;
|
||||
if (!save)
|
||||
continue;
|
||||
|
||||
ObjectGuid guid = ObjectGuid::Create<HighGuid::Player>(row.guidLow);
|
||||
|
||||
PlayerCreateBoundInstancesMaps(guid);
|
||||
InstancePlayerBind& bind = playerBindStorage[guid]->m[save->GetDifficulty()][save->GetMapId()];
|
||||
if (bind.save) // pussywizard: another bind for the same map and difficulty! may happen because of mysql thread races
|
||||
{
|
||||
if (bind.perm) // already loaded perm -> delete currently checked one from db
|
||||
{
|
||||
CharacterDatabasePreparedStatement* stmt = CharacterDatabase.GetPreparedStatement(CHAR_DEL_CHAR_INSTANCE_BY_INSTANCE_GUID);
|
||||
stmt->SetData(0, guid.GetCounter());
|
||||
stmt->SetData(1, row.instanceId);
|
||||
CharacterDatabase.Execute(stmt);
|
||||
continue;
|
||||
}
|
||||
else // override temp bind by newest one
|
||||
{
|
||||
CharacterDatabasePreparedStatement* stmt = CharacterDatabase.GetPreparedStatement(CHAR_DEL_CHAR_INSTANCE_BY_INSTANCE_GUID);
|
||||
stmt->SetData(0, guid.GetCounter());
|
||||
stmt->SetData(1, bind.save->GetInstanceId());
|
||||
CharacterDatabase.Execute(stmt);
|
||||
bind.save->RemovePlayer(guid, this);
|
||||
}
|
||||
}
|
||||
bind.save = save;
|
||||
bind.perm = row.perm;
|
||||
bind.extended = row.extended;
|
||||
save->AddPlayer(guid);
|
||||
if (row.perm)
|
||||
save->SetCanReset(false);
|
||||
}
|
||||
|
||||
lock_instLists = false;
|
||||
}
|
||||
|
||||
void InstanceSaveMgr::ScheduleReset(time_t time, InstResetEvent event)
|
||||
{
|
||||
m_resetTimeQueue.insert(std::pair<time_t, InstResetEvent>(time, event));
|
||||
|
||||
@@ -27,6 +27,7 @@
|
||||
#include <map>
|
||||
#include <mutex>
|
||||
#include <unordered_map>
|
||||
#include <vector>
|
||||
|
||||
struct InstanceTemplate;
|
||||
struct MapEntry;
|
||||
@@ -103,6 +104,31 @@ private:
|
||||
|
||||
typedef std::unordered_map<uint32 /*PAIR32(map, difficulty)*/, time_t /*resetTime*/> ResetTimeByMapDifficultyMap;
|
||||
|
||||
// Raw rows fetched on a worker thread by LoadInstanceSavesAndBindsForMapIDs.
|
||||
// Contains no manager-owned state, so it is safe to build off the world thread
|
||||
// and consume later on the world thread in MergeWithNewInstanceSaves.
|
||||
struct InstanceMapLoadRows
|
||||
{
|
||||
struct InstanceRow
|
||||
{
|
||||
uint32 instanceId;
|
||||
uint16 mapId;
|
||||
time_t resetTime;
|
||||
uint8 difficulty;
|
||||
uint32 completedEncounters;
|
||||
std::string data;
|
||||
};
|
||||
struct BindRow
|
||||
{
|
||||
uint32 guidLow;
|
||||
uint32 instanceId;
|
||||
bool perm;
|
||||
bool extended;
|
||||
};
|
||||
std::vector<InstanceRow> instances;
|
||||
std::vector<BindRow> binds;
|
||||
};
|
||||
|
||||
class InstanceSaveMgr
|
||||
{
|
||||
friend class InstanceSave;
|
||||
@@ -133,6 +159,11 @@ public:
|
||||
void LoadInstanceSaves();
|
||||
void LoadCharacterBinds();
|
||||
|
||||
// Worker thread: performs only the (blocking) DB reads, touches no manager state.
|
||||
[[nodiscard]] InstanceMapLoadRows LoadInstanceSavesAndBindsForMapIDs(std::vector<uint32> const& mapIDs);
|
||||
// World thread: builds InstanceSaves and player binds from the fetched rows and merges them in.
|
||||
void MergeWithNewInstanceSaves(InstanceMapLoadRows const& loadResult);
|
||||
|
||||
[[nodiscard]] time_t GetResetTimeFor(uint32 mapid, Difficulty d) const
|
||||
{
|
||||
ResetTimeByMapDifficultyMap::const_iterator itr = m_resetTimeByMapDifficulty.find(MAKE_PAIR32(mapid, d));
|
||||
|
||||
@@ -31,6 +31,7 @@
|
||||
#include "RBAC.h"
|
||||
#include "ScriptMgr.h"
|
||||
#include "Spell.h"
|
||||
#include "TC9Sidecar.h"
|
||||
#include "WorldSession.h"
|
||||
|
||||
BossBoundaryData::~BossBoundaryData()
|
||||
@@ -41,6 +42,9 @@ BossBoundaryData::~BossBoundaryData()
|
||||
|
||||
void InstanceScript::SaveToDB()
|
||||
{
|
||||
if (sToCloud9Sidecar->ClusterModeEnabled() && !sToCloud9Sidecar->IsMapAssigned(instance->GetEntry()->MapID))
|
||||
return;
|
||||
|
||||
std::string data = GetSaveData();
|
||||
//if (data.empty()) // pussywizard: encounterMask can be updated and theres no reason to not save
|
||||
// return;
|
||||
|
||||
@@ -38,6 +38,7 @@
|
||||
#include "Pet.h"
|
||||
#include "PoolMgr.h"
|
||||
#include "ScriptMgr.h"
|
||||
#include "TC9Sidecar.h"
|
||||
#include "Transport.h"
|
||||
#include "VMapFactory.h"
|
||||
#include "Vehicle.h"
|
||||
@@ -518,6 +519,8 @@ void Map::Update(const uint32 t_diff, const uint32 s_diff, bool /*thread*/)
|
||||
|
||||
HandleDelayedVisibility();
|
||||
|
||||
UpdatePlayersRedirectKickEvent(t_diff);
|
||||
|
||||
UpdateWeather(t_diff);
|
||||
UpdateExpiredCorpses(t_diff);
|
||||
|
||||
@@ -1389,15 +1392,15 @@ LiquidData const Map::GetLiquidData(uint32 phaseMask, float x, float y, float z,
|
||||
return liquidData;
|
||||
}
|
||||
|
||||
void Map::GetFullTerrainStatusForPosition(uint32 /*phaseMask*/, float x, float y, float z, float collisionHeight, PositionFullTerrainStatus& data, Optional<uint8> reqLiquidType)
|
||||
void Map::GetFullTerrainStatusForPosition(uint32 phaseMask, float x, float y, float z, float collisionHeight, PositionFullTerrainStatus& data, Optional<uint8> reqLiquidType)
|
||||
{
|
||||
GridTerrainData* gmap = GetGridTerrainData(x, y);
|
||||
|
||||
VMAP::AreaAndLiquidData vmapData;
|
||||
// VMAP::AreaAndLiquidData dynData;
|
||||
VMAP::AreaAndLiquidData dynData;
|
||||
VMAP::AreaAndLiquidData* wmoData = nullptr;
|
||||
_mapCollisionData.GetStaticTree().GetAreaAndLiquidData(x, y, z, reqLiquidType, vmapData);
|
||||
// _dynamicTree.GetAreaAndLiquidData(x, y, z, phaseMask, reqLiquidType, dynData);
|
||||
_mapCollisionData.GetDynamicTree().GetAreaAndLiquidData(x, y, z, phaseMask, reqLiquidType, dynData);
|
||||
|
||||
uint32 gridAreaId = 0;
|
||||
float gridMapHeight = INVALID_HEIGHT;
|
||||
@@ -1424,7 +1427,6 @@ void Map::GetFullTerrainStatusForPosition(uint32 /*phaseMask*/, float x, float y
|
||||
// NOTE: Objects will not detect a case when a wmo providing area/liquid despawns from under them
|
||||
// but this is fine as these kind of objects are not meant to be spawned and despawned a lot
|
||||
// example: Lich King platform
|
||||
/*
|
||||
if (dynData.floorZ > VMAP_INVALID_HEIGHT && G3D::fuzzyGe(z, dynData.floorZ - GROUND_HEIGHT_TOLERANCE) &&
|
||||
(G3D::fuzzyLt(z, gridMapHeight - GROUND_HEIGHT_TOLERANCE) || dynData.floorZ > gridMapHeight) &&
|
||||
(G3D::fuzzyLt(z, vmapData.floorZ - GROUND_HEIGHT_TOLERANCE) || dynData.floorZ > vmapData.floorZ))
|
||||
@@ -1432,7 +1434,6 @@ void Map::GetFullTerrainStatusForPosition(uint32 /*phaseMask*/, float x, float y
|
||||
data.floorZ = dynData.floorZ;
|
||||
wmoData = &dynData;
|
||||
}
|
||||
*/
|
||||
|
||||
if (wmoData)
|
||||
{
|
||||
@@ -1672,7 +1673,7 @@ void Map::SendInitTransports(Player* player)
|
||||
// Hack to send out transports
|
||||
UpdateData transData;
|
||||
for (TransportsContainer::const_iterator itr = _transports.begin(); itr != _transports.end(); ++itr)
|
||||
if (*itr != player->GetTransport())
|
||||
if (*itr != player->GetTransport() && (!sToCloud9Sidecar->ClusterModeEnabled() || player->InSamePhase(*itr)))
|
||||
(*itr)->BuildCreateUpdateBlockForPlayer(&transData, player);
|
||||
|
||||
if (!transData.HasData())
|
||||
@@ -1857,6 +1858,50 @@ uint32 Map::GetPlayersCountExceptGMs(bool aliveOnly /*= false*/) const
|
||||
return count;
|
||||
}
|
||||
|
||||
void Map::StartPlayersRedirectKickTimer()
|
||||
{
|
||||
for (MapRefMgr::iterator itr = m_mapRefMgr.begin(); itr != m_mapRefMgr.end(); ++itr)
|
||||
itr->GetSource()->SendSystemMessage("Preparing to enter parallel dimension... One minute!\nAccelerate transfer: Teleport or type \"/ready\" in chat.");
|
||||
|
||||
_redirectKickTimer.Reset(60 * SECOND * IN_MILLISECONDS);
|
||||
_lastAnnounceRedirectKickTimer.Reset(55 * SECOND * IN_MILLISECONDS);
|
||||
|
||||
_lastAnnounceRedirectKickTimer.Update(1);
|
||||
|
||||
}
|
||||
|
||||
void Map::StopPlayersRedirectKickTimer()
|
||||
{
|
||||
_redirectKickTimer.Reset(0);
|
||||
_lastAnnounceRedirectKickTimer.Reset(0);
|
||||
}
|
||||
|
||||
void Map::UpdatePlayersRedirectKickEvent(uint32 diff)
|
||||
{
|
||||
if (_redirectKickTimer.Passed())
|
||||
return;
|
||||
|
||||
_redirectKickTimer.Update(diff);
|
||||
|
||||
if (_redirectKickTimer.Passed())
|
||||
{
|
||||
auto emptyPacket = WorldPacket();
|
||||
for (MapRefMgr::iterator itr = m_mapRefMgr.begin(); itr != m_mapRefMgr.end(); ++itr)
|
||||
itr->GetSource()->GetSession()->HandleTC9PrepareForRedirect(emptyPacket);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (_lastAnnounceRedirectKickTimer.Passed())
|
||||
return;
|
||||
|
||||
_lastAnnounceRedirectKickTimer.Update(diff);
|
||||
|
||||
if (_lastAnnounceRedirectKickTimer.Passed())
|
||||
for (MapRefMgr::iterator itr = m_mapRefMgr.begin(); itr != m_mapRefMgr.end(); ++itr)
|
||||
itr->GetSource()->SendSystemMessage("Dimensional shift incoming! Prepare to transition in 5 seconds...");
|
||||
}
|
||||
|
||||
void Map::SendToPlayers(WorldPacket const* data) const
|
||||
{
|
||||
for (MapRefMgr::const_iterator itr = m_mapRefMgr.begin(); itr != m_mapRefMgr.end(); ++itr)
|
||||
|
||||
@@ -27,6 +27,7 @@
|
||||
#include "GameObjectModel.h"
|
||||
#include "GridDefines.h"
|
||||
#include "GridRefMgr.h"
|
||||
#include "Timer.h"
|
||||
#include "MapCollisionData.h"
|
||||
#include "MapGridManager.h"
|
||||
#include "MapRefMgr.h"
|
||||
@@ -325,6 +326,10 @@ public:
|
||||
|
||||
void SendToPlayers(WorldPacket const* data) const;
|
||||
|
||||
void StartPlayersRedirectKickTimer();
|
||||
void StopPlayersRedirectKickTimer();
|
||||
bool IsPlayerRedirectKickTimerActive() { return !_redirectKickTimer.Passed(); }
|
||||
|
||||
typedef MapRefMgr PlayerList;
|
||||
[[nodiscard]] PlayerList const& GetPlayers() const { return m_mapRefMgr; }
|
||||
|
||||
@@ -585,6 +590,8 @@ private:
|
||||
|
||||
void SendObjectUpdates();
|
||||
|
||||
void UpdatePlayersRedirectKickEvent(uint32 diff);
|
||||
|
||||
protected:
|
||||
// Type specific code for add/remove to/from grid
|
||||
template<class T>
|
||||
@@ -693,6 +700,9 @@ private:
|
||||
PendingAddUpdatableObjectList _pendingAddUpdatableObjectList;
|
||||
IntervalTimer _updatableObjectListRecheckTimer;
|
||||
ZoneWideVisibleWorldObjectsMap _zoneWideVisibleWorldObjectsMap;
|
||||
|
||||
TimeTrackerSmall _redirectKickTimer;
|
||||
TimeTrackerSmall _lastAnnounceRedirectKickTimer;
|
||||
};
|
||||
|
||||
enum InstanceResetMethod
|
||||
|
||||
@@ -31,6 +31,7 @@
|
||||
#include "Opcodes.h"
|
||||
#include "Player.h"
|
||||
#include "ScriptMgr.h"
|
||||
#include "TC9Sidecar.h"
|
||||
#include "Transport.h"
|
||||
#include "World.h"
|
||||
#include "WorldPacket.h"
|
||||
@@ -405,6 +406,9 @@ void MapMgr::RegisterInstanceId(uint32 instanceId)
|
||||
|
||||
uint32 MapMgr::GenerateInstanceId()
|
||||
{
|
||||
if (sToCloud9Sidecar->ClusterModeEnabled())
|
||||
return sToCloud9Sidecar->GenerateInstanceGuid();
|
||||
|
||||
uint32 newInstanceId = _nextInstanceId;
|
||||
|
||||
// find the lowest available id starting from the current _nextInstanceId
|
||||
|
||||
@@ -21,6 +21,9 @@
|
||||
#include "MoveSpline.h"
|
||||
#include "QueryResult.h"
|
||||
#include "Transport.h"
|
||||
#include "TaskScheduler.h"
|
||||
#include "Config.h"
|
||||
#include <chrono>
|
||||
|
||||
TransportTemplate::~TransportTemplate()
|
||||
{
|
||||
|
||||
@@ -508,6 +508,10 @@ bool WaypointMovementGenerator<Creature>::GetResetPosition(float& x, float& y, f
|
||||
if (!i_path || i_path->Nodes.empty())
|
||||
return false;
|
||||
|
||||
// A finished non-repeating path no longer owns the creature's reset position.
|
||||
if (_done)
|
||||
return false;
|
||||
|
||||
ASSERT(i_currentNode < i_path->Nodes.size(), "WaypointMovementGenerator::GetResetPos: tried to reference a node id ({}) which is not included in path ({})", i_currentNode, i_path->Id);
|
||||
WaypointNode const& waypoint = i_path->Nodes.at(i_currentNode);
|
||||
|
||||
|
||||
@@ -526,28 +526,14 @@ void PoolGroup<Quest>::SpawnObject(ActivePoolData& spawns, uint32 limit, uint32
|
||||
sPoolMgr->SaveQuestsToDB(false, false, true);
|
||||
}
|
||||
|
||||
// Method that does the respawn job on the specified creature
|
||||
template <>
|
||||
void PoolGroup<Creature>::ReSpawn1Object(PoolObject* obj)
|
||||
// Method that does the respawn job on the specified object
|
||||
template <typename T>
|
||||
void PoolGroup<T>::ReSpawn1Object(PoolObject* obj)
|
||||
{
|
||||
Despawn1Object(obj->guid);
|
||||
Spawn1Object(obj);
|
||||
}
|
||||
|
||||
// Method that does the respawn job on the specified gameobject
|
||||
template <>
|
||||
void PoolGroup<GameObject>::ReSpawn1Object(PoolObject* obj)
|
||||
{
|
||||
Despawn1Object(obj->guid);
|
||||
Spawn1Object(obj);
|
||||
}
|
||||
|
||||
// Nothing to do for a child Pool
|
||||
template <>
|
||||
void PoolGroup<Pool>::ReSpawn1Object(PoolObject* /*obj*/)
|
||||
{
|
||||
}
|
||||
|
||||
// Nothing to do for a quest
|
||||
template <>
|
||||
void PoolGroup<Quest>::ReSpawn1Object(PoolObject* /*obj*/)
|
||||
|
||||
@@ -1439,6 +1439,8 @@ void OpcodeTable::Initialize()
|
||||
/*0x51C*/ DEFINE_SERVER_OPCODE_HANDLER(SMSG_COMMENTATOR_SKIRMISH_QUEUE_RESULT1, STATUS_NEVER);
|
||||
/*0x51D*/ DEFINE_SERVER_OPCODE_HANDLER(SMSG_COMMENTATOR_SKIRMISH_QUEUE_RESULT2, STATUS_NEVER);
|
||||
/*0x51E*/ DEFINE_SERVER_OPCODE_HANDLER(SMSG_MULTIPLE_MOVES, STATUS_NEVER);
|
||||
/*0x51F*/ DEFINE_HANDLER(TC9_CMSG_PREPARE_FOR_REDIRECT, STATUS_AUTHED, PROCESS_THREADUNSAFE, &WorldSession::HandleTC9PrepareForRedirect);
|
||||
/*0x520*/ DEFINE_SERVER_OPCODE_HANDLER(TC9_SMSG_READY_FOR_REDIRECT, STATUS_NEVER);
|
||||
|
||||
#undef DEFINE_HANDLER
|
||||
#undef DEFINE_SERVER_OPCODE_HANDLER
|
||||
|
||||
@@ -1338,7 +1338,9 @@ enum Opcodes : uint16
|
||||
SMSG_COMMENTATOR_SKIRMISH_QUEUE_RESULT1 = 0x51C,
|
||||
SMSG_COMMENTATOR_SKIRMISH_QUEUE_RESULT2 = 0x51D,
|
||||
SMSG_MULTIPLE_MOVES = 0x51E, // uncompressed version of SMSG_COMPRESSED_MOVES
|
||||
NUM_MSG_TYPES = 0x51F
|
||||
TC9_CMSG_PREPARE_FOR_REDIRECT = 0x51F,
|
||||
TC9_SMSG_READY_FOR_REDIRECT = 0x520,
|
||||
NUM_MSG_TYPES = 0x521
|
||||
};
|
||||
|
||||
enum OpcodeMisc : uint16
|
||||
|
||||
@@ -19,6 +19,7 @@
|
||||
\ingroup u2w
|
||||
*/
|
||||
|
||||
#include "TC9Sidecar.h"
|
||||
#include "WorldSession.h"
|
||||
#include "AccountMgr.h"
|
||||
#include "BattlegroundMgr.h"
|
||||
@@ -700,7 +701,7 @@ void WorldSession::SendPlayTimeWarning(PlayTimeFlag flag, int32 playTimeRemainin
|
||||
}
|
||||
|
||||
/// %Log the player out
|
||||
void WorldSession::LogoutPlayer(bool save)
|
||||
void WorldSession::LogoutPlayer(bool save, bool redirecting)
|
||||
{
|
||||
// finish pending transfers before starting the logout
|
||||
while (_player && _player->IsBeingTeleportedFar())
|
||||
@@ -788,23 +789,26 @@ void WorldSession::LogoutPlayer(bool save)
|
||||
// there are some positive auras from boss encounters that can be kept by logging out and logging in after boss is dead, and may be used on next bosses
|
||||
_player->RemoveAurasWithInterruptFlags(AURA_INTERRUPT_FLAG_CHANGE_MAP);
|
||||
|
||||
if (Group *group = _player->GetGroupInvite())
|
||||
sWorld->getBoolConfig(CONFIG_LEAVE_GROUP_ON_LOGOUT)
|
||||
? _player->UninviteFromGroup() // Can disband group.
|
||||
: group->RemoveInvite(_player); // Just removes invite.
|
||||
if (!redirecting)
|
||||
{
|
||||
if (Group *group = _player->GetGroupInvite())
|
||||
sWorld->getBoolConfig(CONFIG_LEAVE_GROUP_ON_LOGOUT)
|
||||
? _player->UninviteFromGroup() // Can disband group.
|
||||
: group->RemoveInvite(_player); // Just removes invite.
|
||||
|
||||
// remove player from the group if he is:
|
||||
// a) in group; b) not in raid group; c) logging out normally (not being kicked or disconnected) d) LeaveGroupOnLogout is enabled
|
||||
if (_player->GetGroup() && !_player->GetGroup()->isRaidGroup() && !_player->GetGroup()->isLFGGroup() && m_Socket && sWorld->getBoolConfig(CONFIG_LEAVE_GROUP_ON_LOGOUT))
|
||||
_player->RemoveFromGroup();
|
||||
// Remove player from active loot rolls in LFG groups (player stays in group but should not block rolls)
|
||||
else if (Group* group = _player->GetGroup())
|
||||
if (group->isLFGGroup())
|
||||
group->RemovePlayerFromRolls(_player->GetGUID());
|
||||
// remove player from the group if he is:
|
||||
// a) in group; b) not in raid group; c) logging out normally (not being kicked or disconnected) d) LeaveGroupOnLogout is enabled
|
||||
if (!sToCloud9Sidecar->ClusterModeEnabled() && _player->GetGroup() && !_player->GetGroup()->isRaidGroup() && !_player->GetGroup()->isLFGGroup() && m_Socket && sWorld->getBoolConfig(CONFIG_LEAVE_GROUP_ON_LOGOUT))
|
||||
_player->RemoveFromGroup();
|
||||
// Remove player from active loot rolls in LFG groups (player stays in group but should not block rolls)
|
||||
else if (Group* group = _player->GetGroup())
|
||||
if (group->isLFGGroup())
|
||||
group->RemovePlayerFromRolls(_player->GetGUID());
|
||||
|
||||
// pussywizard: checked second time after being removed from a group
|
||||
if (!_player->IsBeingTeleportedFar() && !_player->m_InstanceValid && !_player->IsGameMaster())
|
||||
_player->RepopAtGraveyard();
|
||||
// pussywizard: checked second time after being removed from a group
|
||||
if (!_player->IsBeingTeleportedFar() && !_player->m_InstanceValid && !_player->IsGameMaster())
|
||||
_player->RepopAtGraveyard();
|
||||
}
|
||||
|
||||
// Repop at Graveyard or other player far teleport will prevent saving player because of not present map
|
||||
// Teleport player immediately for correct player save
|
||||
@@ -843,12 +847,15 @@ void WorldSession::LogoutPlayer(bool save)
|
||||
}
|
||||
}
|
||||
|
||||
//! Broadcast a logout message to the player's friends
|
||||
sSocialMgr->SendFriendStatus(_player, FRIEND_OFFLINE, _player->GetGUID(), true);
|
||||
sSocialMgr->RemovePlayerSocial(_player->GetGUID());
|
||||
if (!redirecting)
|
||||
{
|
||||
//! Broadcast a logout message to the player's friends
|
||||
sSocialMgr->SendFriendStatus(_player, FRIEND_OFFLINE, _player->GetGUID(), true);
|
||||
sSocialMgr->RemovePlayerSocial(_player->GetGUID());
|
||||
|
||||
//! Call script hook before deletion
|
||||
sScriptMgr->OnPlayerLogout(_player);
|
||||
//! Call script hook before deletion
|
||||
sScriptMgr->OnPlayerLogout(_player);
|
||||
}
|
||||
|
||||
METRIC_EVENT("player_events", "Logout", _player->GetName());
|
||||
|
||||
@@ -878,9 +885,12 @@ void WorldSession::LogoutPlayer(bool save)
|
||||
LOG_DEBUG("network", "SESSION: Sent SMSG_LOGOUT_COMPLETE Message");
|
||||
|
||||
//! Since each account can only have one online character at any given time, ensure all characters for active account are marked as offline
|
||||
CharacterDatabasePreparedStatement* stmt = CharacterDatabase.GetPreparedStatement(CharacterDatabaseStatements(statementIndex));
|
||||
stmt->SetData(0, statementParam);
|
||||
CharacterDatabase.Execute(stmt);
|
||||
if (!redirecting)
|
||||
{
|
||||
CharacterDatabasePreparedStatement* stmt = CharacterDatabase.GetPreparedStatement(CharacterDatabaseStatements(statementIndex));
|
||||
stmt->SetData(0, statementParam);
|
||||
CharacterDatabase.Execute(stmt);
|
||||
}
|
||||
}
|
||||
|
||||
m_playerLogout = false;
|
||||
@@ -1596,21 +1606,69 @@ void WorldSession::InitializeSessionCallback(CharacterDatabaseQueryHolder const&
|
||||
LoadAccountData(realmHolder.GetPreparedResult(AccountInfoQueryHolderPerRealm::GLOBAL_ACCOUNT_DATA), GLOBAL_CACHE_MASK);
|
||||
LoadTutorialsData(realmHolder.GetPreparedResult(AccountInfoQueryHolderPerRealm::TUTORIALS));
|
||||
|
||||
if (!m_inQueue)
|
||||
if (!sToCloud9Sidecar->ClusterModeEnabled())
|
||||
{
|
||||
SendAuthResponse(AUTH_OK, true);
|
||||
}
|
||||
else
|
||||
{
|
||||
SendAuthWaitQueue(0);
|
||||
if (!m_inQueue)
|
||||
{
|
||||
SendAuthResponse(AUTH_OK, true);
|
||||
}
|
||||
else
|
||||
{
|
||||
SendAuthWaitQueue(0);
|
||||
}
|
||||
}
|
||||
|
||||
SetInQueue(false);
|
||||
ResetTimeOutTime(false);
|
||||
|
||||
SendAddonsInfo();
|
||||
SendClientCacheVersion(clientCacheVersion);
|
||||
SendTutorialsData();
|
||||
if (!sToCloud9Sidecar->ClusterModeEnabled())
|
||||
{
|
||||
SendAddonsInfo();
|
||||
SendClientCacheVersion(clientCacheVersion);
|
||||
SendTutorialsData();
|
||||
}
|
||||
}
|
||||
|
||||
void WorldSession::HandleTC9PrepareForRedirect(WorldPacket& /*recvData*/)
|
||||
{
|
||||
if (!sToCloud9Sidecar->ClusterModeEnabled())
|
||||
return;
|
||||
|
||||
Player* player = this->GetPlayer();
|
||||
if (player == nullptr)
|
||||
{
|
||||
WorldPacket data(TC9_SMSG_READY_FOR_REDIRECT, 1);
|
||||
data << uint8(1); // 1 - Failed.
|
||||
SendPacket(&data);
|
||||
return;
|
||||
}
|
||||
|
||||
LOG_DEBUG("network", "Starting saving, AccountId = {}", GetAccountId());
|
||||
|
||||
CharacterDatabaseTransaction trans = CharacterDatabase.BeginTransaction();
|
||||
player->SaveToDB(trans, false, true);
|
||||
AddTransactionCallback(CharacterDatabase.AsyncCommitTransaction(trans)).AfterComplete([this](bool success)
|
||||
{
|
||||
WorldPacket data(TC9_SMSG_READY_FOR_REDIRECT, 1);
|
||||
data << uint8(!success); // 0 - Success, 1 - Failed.
|
||||
SendPacket(&data);
|
||||
|
||||
if (!success)
|
||||
{
|
||||
LOG_ERROR("network", "Failed to save player, AccountId = {}", GetAccountId());
|
||||
return;
|
||||
}
|
||||
|
||||
LOG_DEBUG("network", "Saved, AccountId = {}", GetAccountId());
|
||||
|
||||
Player* player = GetPlayer();
|
||||
if (!player)
|
||||
return;
|
||||
|
||||
player->m_Events.AddEventAtOffset([this](){
|
||||
KickPlayer("HandlePrepareForRedirect client redirected");
|
||||
}, 100ms);
|
||||
});
|
||||
}
|
||||
|
||||
void WorldSession::SetPacketLogging(bool state)
|
||||
|
||||
@@ -543,7 +543,7 @@ public:
|
||||
return (_logoutTime > 0 && currTime >= _logoutTime + 20);
|
||||
}
|
||||
|
||||
void LogoutPlayer(bool save);
|
||||
void LogoutPlayer(bool save, bool redirecting = false);
|
||||
void KickPlayer(bool setKicked = true) { return this->KickPlayer("Unknown reason", setKicked); }
|
||||
void KickPlayer(std::string const& reason, bool setKicked = true);
|
||||
|
||||
@@ -706,6 +706,8 @@ public: // opcodes handlers
|
||||
void SendCharFactionChange(ResponseCodes result, CharacterFactionChangeInfo const* factionChangeInfo);
|
||||
void SendSetPlayerDeclinedNamesResult(DeclinedNameResult result, ObjectGuid guid);
|
||||
|
||||
void HandleTC9PrepareForRedirect(WorldPacket& recvData);
|
||||
|
||||
// played time
|
||||
void HandlePlayedTime(WorldPackets::Character::PlayedTimeClient& packet);
|
||||
|
||||
|
||||
@@ -15,6 +15,7 @@
|
||||
* with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include "TC9Sidecar.h"
|
||||
#include "WorldSocket.h"
|
||||
#include "AccountMgr.h"
|
||||
#include "Config.h"
|
||||
@@ -579,8 +580,9 @@ void WorldSocket::HandleAuthSessionCallback(std::shared_ptr<ClientAuthSession> a
|
||||
LoginDatabase.Execute(stmt);
|
||||
// This also allows to check for possible "hack" attempts on account
|
||||
|
||||
// even if auth credentials are bad, try using the session key we have - client cannot read auth response error without it
|
||||
_authCrypt.Init(account.SessionKey);
|
||||
if (!sToCloud9Sidecar->ClusterModeEnabled())
|
||||
// even if auth credentials are bad, try using the session key we have - client cannot read auth response error without it
|
||||
_authCrypt.Init(account.SessionKey);
|
||||
|
||||
// First reject the connection if packet contains invalid data or realm state doesn't allow logging in
|
||||
if (sWorld->IsClosed())
|
||||
@@ -591,7 +593,7 @@ void WorldSocket::HandleAuthSessionCallback(std::shared_ptr<ClientAuthSession> a
|
||||
return;
|
||||
}
|
||||
|
||||
if (authSession->RealmID != realm.Id.Realm)
|
||||
if (!sToCloud9Sidecar->ClusterModeEnabled() && authSession->RealmID != realm.Id.Realm)
|
||||
{
|
||||
SendAuthResponseError(REALM_LIST_REALM_NOT_FOUND);
|
||||
LOG_ERROR("network", "WorldSocket::HandleAuthSession: Client {} requested connecting with realm id {} but this realm has id {} set in config.",
|
||||
@@ -600,105 +602,107 @@ void WorldSocket::HandleAuthSessionCallback(std::shared_ptr<ClientAuthSession> a
|
||||
return;
|
||||
}
|
||||
|
||||
// Must be done before WorldSession is created
|
||||
bool wardenActive = sWorld->getBoolConfig(CONFIG_WARDEN_ENABLED);
|
||||
if (wardenActive && account.OS != "Win" && account.OS != "OSX")
|
||||
if (!sToCloud9Sidecar->ClusterModeEnabled())
|
||||
{
|
||||
SendAuthResponseError(AUTH_REJECT);
|
||||
LOG_ERROR("network", "WorldSocket::HandleAuthSession: Client {} attempted to log in using invalid client OS ({}).", address, account.OS);
|
||||
DelayedCloseSocket();
|
||||
return;
|
||||
}
|
||||
// Must be done before WorldSession is created
|
||||
if (wardenActive && account.OS != "Win" && account.OS != "OSX")
|
||||
{
|
||||
SendAuthResponseError(AUTH_REJECT);
|
||||
LOG_ERROR("network", "WorldSocket::HandleAuthSession: Client {} attempted to log in using invalid client OS ({}).", address, account.OS);
|
||||
DelayedCloseSocket();
|
||||
return;
|
||||
}
|
||||
|
||||
// Check that Key and account name are the same on client and server
|
||||
uint8 t[4] = { 0x00,0x00,0x00,0x00 };
|
||||
// Check that Key and account name are the same on client and server
|
||||
uint8 t[4] = { 0x00,0x00,0x00,0x00 };
|
||||
|
||||
Acore::Crypto::SHA1 sha;
|
||||
sha.UpdateData(authSession->Account);
|
||||
sha.UpdateData(t);
|
||||
sha.UpdateData(authSession->LocalChallenge);
|
||||
sha.UpdateData(_authSeed);
|
||||
sha.UpdateData(account.SessionKey);
|
||||
sha.Finalize();
|
||||
Acore::Crypto::SHA1 sha;
|
||||
sha.UpdateData(authSession->Account);
|
||||
sha.UpdateData(t);
|
||||
sha.UpdateData(authSession->LocalChallenge);
|
||||
sha.UpdateData(_authSeed);
|
||||
sha.UpdateData(account.SessionKey);
|
||||
sha.Finalize();
|
||||
|
||||
if (sha.GetDigest() != authSession->Digest)
|
||||
{
|
||||
SendAuthResponseError(AUTH_FAILED);
|
||||
LOG_ERROR("network", "WorldSocket::HandleAuthSession: Authentication failed for account: {} ('{}') address: {}", account.Id, authSession->Account, address);
|
||||
DelayedCloseSocket();
|
||||
return;
|
||||
}
|
||||
|
||||
if (IpLocationRecord const* location = sIPLocation->GetLocationRecord(address))
|
||||
_ipCountry = location->CountryCode;
|
||||
|
||||
///- Re-check ip locking (same check as in auth).
|
||||
if (account.IsLockedToIP)
|
||||
{
|
||||
if (account.LastIP != address)
|
||||
if (sha.GetDigest() != authSession->Digest)
|
||||
{
|
||||
SendAuthResponseError(AUTH_FAILED);
|
||||
LOG_DEBUG("network", "WorldSocket::HandleAuthSession: Sent Auth Response (Account IP differs. Original IP: {}, new IP: {}).", account.LastIP, address);
|
||||
// We could log on hook only instead of an additional db log, however action logger is config based. Better keep DB logging as well
|
||||
LOG_ERROR("network", "WorldSocket::HandleAuthSession: Authentication failed for account: {} ('{}') address: {}", account.Id, authSession->Account, address);
|
||||
DelayedCloseSocket();
|
||||
return;
|
||||
}
|
||||
|
||||
if (IpLocationRecord const* location = sIPLocation->GetLocationRecord(address))
|
||||
_ipCountry = location->CountryCode;
|
||||
|
||||
///- Re-check ip locking (same check as in auth).
|
||||
if (account.IsLockedToIP)
|
||||
{
|
||||
if (account.LastIP != address)
|
||||
{
|
||||
SendAuthResponseError(AUTH_FAILED);
|
||||
LOG_DEBUG("network", "WorldSocket::HandleAuthSession: Sent Auth Response (Account IP differs. Original IP: {}, new IP: {}).", account.LastIP, address);
|
||||
// We could log on hook only instead of an additional db log, however action logger is config based. Better keep DB logging as well
|
||||
sScriptMgr->OnFailedAccountLogin(account.Id);
|
||||
DelayedCloseSocket();
|
||||
return;
|
||||
}
|
||||
}
|
||||
else if (!account.LockCountry.empty() && account.LockCountry != "00" && !_ipCountry.empty())
|
||||
{
|
||||
if (account.LockCountry != _ipCountry)
|
||||
{
|
||||
SendAuthResponseError(AUTH_FAILED);
|
||||
LOG_DEBUG("network", "WorldSocket::HandleAuthSession: Sent Auth Response (Account country differs. Original country: {}, new country: {}).", account.LockCountry, _ipCountry);
|
||||
// We could log on hook only instead of an additional db log, however action logger is config based. Better keep DB logging as well
|
||||
sScriptMgr->OnFailedAccountLogin(account.Id);
|
||||
DelayedCloseSocket();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
//! Negative mutetime indicates amount of minutes to be muted effective on next login - which is now.
|
||||
if (account.MuteTime < 0)
|
||||
{
|
||||
account.MuteTime = GameTime::GetGameTime().count() + std::llabs(account.MuteTime);
|
||||
|
||||
auto* stmt = LoginDatabase.GetPreparedStatement(LOGIN_UPD_MUTE_TIME_LOGIN);
|
||||
stmt->SetData(0, account.MuteTime);
|
||||
stmt->SetData(1, account.Id);
|
||||
LoginDatabase.Execute(stmt);
|
||||
}
|
||||
|
||||
if (account.IsBanned)
|
||||
{
|
||||
SendAuthResponseError(AUTH_BANNED);
|
||||
LOG_ERROR("network", "WorldSocket::HandleAuthSession: Sent Auth Response (Account banned).");
|
||||
sScriptMgr->OnFailedAccountLogin(account.Id);
|
||||
DelayedCloseSocket();
|
||||
return;
|
||||
}
|
||||
}
|
||||
else if (!account.LockCountry.empty() && account.LockCountry != "00" && !_ipCountry.empty())
|
||||
{
|
||||
if (account.LockCountry != _ipCountry)
|
||||
|
||||
// Check locked state for server
|
||||
AccountTypes allowedAccountType = sWorld->GetPlayerSecurityLimit();
|
||||
LOG_DEBUG("network", "Allowed Level: {} Player Level {}", allowedAccountType, account.Security);
|
||||
if (allowedAccountType > SEC_PLAYER && account.Security < allowedAccountType)
|
||||
{
|
||||
SendAuthResponseError(AUTH_FAILED);
|
||||
LOG_DEBUG("network", "WorldSocket::HandleAuthSession: Sent Auth Response (Account country differs. Original country: {}, new country: {}).", account.LockCountry, _ipCountry);
|
||||
// We could log on hook only instead of an additional db log, however action logger is config based. Better keep DB logging as well
|
||||
SendAuthResponseError(AUTH_UNAVAILABLE);
|
||||
LOG_DEBUG("network", "WorldSocket::HandleAuthSession: User tries to login but his security level is not enough");
|
||||
sScriptMgr->OnFailedAccountLogin(account.Id);
|
||||
DelayedCloseSocket();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
//! Negative mutetime indicates amount of minutes to be muted effective on next login - which is now.
|
||||
if (account.MuteTime < 0)
|
||||
{
|
||||
account.MuteTime = GameTime::GetGameTime().count() + std::llabs(account.MuteTime);
|
||||
LOG_DEBUG("network", "WorldSocket::HandleAuthSession: Client '{}' authenticated successfully from {}.", authSession->Account, address);
|
||||
|
||||
auto* stmt = LoginDatabase.GetPreparedStatement(LOGIN_UPD_MUTE_TIME_LOGIN);
|
||||
stmt->SetData(0, account.MuteTime);
|
||||
stmt->SetData(1, account.Id);
|
||||
// Update the last_ip in the database as it was successful for login
|
||||
stmt = LoginDatabase.GetPreparedStatement(LOGIN_UPD_LAST_IP);
|
||||
stmt->SetData(0, address);
|
||||
stmt->SetData(1, authSession->Account);
|
||||
LoginDatabase.Execute(stmt);
|
||||
}
|
||||
|
||||
if (account.IsBanned)
|
||||
{
|
||||
SendAuthResponseError(AUTH_BANNED);
|
||||
LOG_ERROR("network", "WorldSocket::HandleAuthSession: Sent Auth Response (Account banned).");
|
||||
sScriptMgr->OnFailedAccountLogin(account.Id);
|
||||
DelayedCloseSocket();
|
||||
return;
|
||||
}
|
||||
|
||||
// Check locked state for server
|
||||
AccountTypes allowedAccountType = sWorld->GetPlayerSecurityLimit();
|
||||
LOG_DEBUG("network", "Allowed Level: {} Player Level {}", allowedAccountType, account.Security);
|
||||
if (allowedAccountType > SEC_PLAYER && account.Security < allowedAccountType)
|
||||
{
|
||||
SendAuthResponseError(AUTH_UNAVAILABLE);
|
||||
LOG_DEBUG("network", "WorldSocket::HandleAuthSession: User tries to login but his security level is not enough");
|
||||
sScriptMgr->OnFailedAccountLogin(account.Id);
|
||||
DelayedCloseSocket();
|
||||
return;
|
||||
}
|
||||
|
||||
LOG_DEBUG("network", "WorldSocket::HandleAuthSession: Client '{}' authenticated successfully from {}.", authSession->Account, address);
|
||||
|
||||
// Update the last_ip in the database as it was successful for login
|
||||
stmt = LoginDatabase.GetPreparedStatement(LOGIN_UPD_LAST_IP);
|
||||
stmt->SetData(0, address);
|
||||
stmt->SetData(1, authSession->Account);
|
||||
|
||||
LoginDatabase.Execute(stmt);
|
||||
|
||||
// At this point, we can safely hook a successful login
|
||||
sScriptMgr->OnAccountLogin(account.Id);
|
||||
|
||||
@@ -712,8 +716,9 @@ void WorldSocket::HandleAuthSessionCallback(std::shared_ptr<ClientAuthSession> a
|
||||
_worldSession->ReadAddonsInfo(authSession->AddonInfo);
|
||||
|
||||
// Initialize Warden system only if it is enabled by config
|
||||
if (wardenActive)
|
||||
if (!sToCloud9Sidecar->ClusterModeEnabled() && wardenActive)
|
||||
{
|
||||
// TODO: move warden outside of a node?
|
||||
_worldSession->InitWarden(account.SessionKey, account.OS);
|
||||
}
|
||||
|
||||
|
||||
@@ -3932,17 +3932,6 @@ void Spell::EffectScriptEffect(SpellEffIndex effIndex)
|
||||
}
|
||||
break;
|
||||
}*/
|
||||
// Roll Dice - Decahedral Dwarven Dice
|
||||
case 47770:
|
||||
{
|
||||
char buf[128];
|
||||
char const* gender = "his";
|
||||
if (m_caster->getGender() > 0)
|
||||
gender = "her";
|
||||
snprintf(buf, sizeof(buf), "%s rubs %s [Decahedral Dwarven Dice] between %s hands and rolls. One %u and one %u.", m_caster->GetName().c_str(), gender, gender, urand(1, 10), urand(1, 10));
|
||||
m_caster->TextEmote(buf);
|
||||
break;
|
||||
}
|
||||
case 52173: // Coyote Spirit Despawn
|
||||
case 60243: // Blood Parrot Despawn
|
||||
if (unitTarget->IsCreature() && unitTarget->ToCreature()->IsSummon())
|
||||
|
||||
@@ -1778,18 +1778,6 @@ void SpellMgr::LoadSpellInfoCorrections()
|
||||
spellInfo->Effects[EFFECT_0].TargetB = SpellImplicitTargetInfo(TARGET_UNIT_SRC_AREA_ALLY);
|
||||
});
|
||||
|
||||
// Lava Strike damage
|
||||
ApplySpellFix({ 57697 }, [](SpellInfo* spellInfo)
|
||||
{
|
||||
spellInfo->Effects[EFFECT_0].TargetA = SpellImplicitTargetInfo(TARGET_DEST_DEST);
|
||||
});
|
||||
|
||||
// Lava Strike trigger
|
||||
ApplySpellFix({ 57578 }, [](SpellInfo* spellInfo)
|
||||
{
|
||||
spellInfo->MaxAffectedTargets = 1;
|
||||
});
|
||||
|
||||
// Gift of Twilight Shadow/Fire
|
||||
ApplySpellFix({ 57835, 58766 }, [](SpellInfo* spellInfo)
|
||||
{
|
||||
@@ -5242,6 +5230,18 @@ void SpellMgr::LoadSpellInfoCorrections()
|
||||
spellInfo->Effects[EFFECT_0].TargetA = SpellImplicitTargetInfo(TARGET_UNIT_TARGET_ANY);
|
||||
});
|
||||
|
||||
// Boulder Assault (Sorlof's Booty)
|
||||
ApplySpellFix({ 44966 }, [](SpellInfo* spellInfo)
|
||||
{
|
||||
spellInfo->AttributesEx |= SPELL_ATTR1_NO_THREAT;
|
||||
});
|
||||
|
||||
// Cannon Assault (Sorlof's Booty)
|
||||
ApplySpellFix({ 45008 }, [](SpellInfo* spellInfo)
|
||||
{
|
||||
spellInfo->AttributesEx3 |= SPELL_ATTR3_ALWAYS_HIT;
|
||||
});
|
||||
|
||||
for (uint32 i = 0; i < GetSpellInfoStoreSize(); ++i)
|
||||
{
|
||||
SpellInfo* spellInfo = mSpellInfoMap[i];
|
||||
|
||||
@@ -3507,268 +3507,6 @@ void SpellMgr::LoadSpellInfoCustomAttributes()
|
||||
|
||||
switch (spellInfo->Id)
|
||||
{
|
||||
// Xinef: additional spells which should be binary
|
||||
case 45145: // Snake Trap Effect
|
||||
spellInfo->AttributesCu |= SPELL_ATTR0_CU_BINARY_SPELL;
|
||||
break;
|
||||
case 1776: // Gouge
|
||||
case 1777:
|
||||
case 8629:
|
||||
case 11285:
|
||||
case 11286:
|
||||
case 12540:
|
||||
case 13579:
|
||||
case 24698:
|
||||
case 28456:
|
||||
case 29425:
|
||||
case 34940:
|
||||
case 36862:
|
||||
case 38764:
|
||||
case 38863:
|
||||
case 52743: // Head Smack
|
||||
spellInfo->AttributesCu |= SPELL_ATTR0_CU_REQ_TARGET_FACING_CASTER;
|
||||
break;
|
||||
case 53: // Backstab
|
||||
case 2589:
|
||||
case 2590:
|
||||
case 2591:
|
||||
case 7159:
|
||||
case 8627:
|
||||
case 8721:
|
||||
case 11279:
|
||||
case 11280:
|
||||
case 11281:
|
||||
case 15582:
|
||||
case 15657:
|
||||
case 22416:
|
||||
case 25300:
|
||||
case 26863:
|
||||
case 37685:
|
||||
case 48656:
|
||||
case 48657:
|
||||
case 703: // Garrote
|
||||
case 8631:
|
||||
case 8632:
|
||||
case 8633:
|
||||
case 11289:
|
||||
case 11290:
|
||||
case 26839:
|
||||
case 26884:
|
||||
case 48675:
|
||||
case 48676:
|
||||
case 5221: // Shred
|
||||
case 6800:
|
||||
case 8992:
|
||||
case 9829:
|
||||
case 9830:
|
||||
case 27001:
|
||||
case 27002:
|
||||
case 48571:
|
||||
case 48572:
|
||||
case 8676: // Ambush
|
||||
case 8724:
|
||||
case 8725:
|
||||
case 11267:
|
||||
case 11268:
|
||||
case 11269:
|
||||
case 27441:
|
||||
case 48689:
|
||||
case 48690:
|
||||
case 48691:
|
||||
case 6785: // Ravage
|
||||
case 6787:
|
||||
case 9866:
|
||||
case 9867:
|
||||
case 27005:
|
||||
case 48578:
|
||||
case 48579:
|
||||
case 21987: // Lash of Pain
|
||||
case 23959: // Test Stab R50
|
||||
case 24825: // Test Backstab
|
||||
case 58563: // Assassinate Restless Lookout
|
||||
case 63124: // quest There's Something About the Squire (13654)
|
||||
spellInfo->AttributesCu |= SPELL_ATTR0_CU_REQ_CASTER_BEHIND_TARGET;
|
||||
break;
|
||||
case 26029: // Dark Glare
|
||||
case 43140: // Flame Breath
|
||||
case 43215: // Flame Breath
|
||||
case 70461: // Coldflame Trap
|
||||
case 72133: // Pain and Suffering
|
||||
case 73788: // Pain and Suffering
|
||||
case 73789: // Pain and Suffering
|
||||
case 73790: // Pain and Suffering
|
||||
case 63293: // Mimiron - spinning damage
|
||||
case 68873: // Wailing Souls
|
||||
case 70324: // Wailing Souls
|
||||
case 64619: // Ulduar, Mimiron, Emergency Fire Bot, Water Spray
|
||||
spellInfo->AttributesCu |= SPELL_ATTR0_CU_CONE_LINE;
|
||||
break;
|
||||
case 58690: // Cyanigosa, Tail Sweep
|
||||
case 59283: // Cyanigosa, Tail Sweep
|
||||
spellInfo->AttributesCu |= SPELL_ATTR0_CU_CONE_BACK;
|
||||
break;
|
||||
case 24340: // Meteor
|
||||
case 26558: // Meteor
|
||||
case 28884: // Meteor
|
||||
case 36837: // Meteor
|
||||
case 38903: // Meteor
|
||||
case 41276: // Meteor
|
||||
case 57467: // Meteor
|
||||
case 26789: // Shard of the Fallen Star
|
||||
case 31436: // Malevolent Cleave
|
||||
case 40810: // Saber Lash
|
||||
case 43267: // Saber Lash
|
||||
case 43268: // Saber Lash
|
||||
case 42384: // Brutal Swipe
|
||||
case 45150: // Meteor Slash
|
||||
case 64688: // Sonic Screech
|
||||
case 72373: // Shared Suffering
|
||||
case 71904: // Chaos Bane
|
||||
case 70492: // Ooze Eruption
|
||||
case 72505: // Ooze Eruption
|
||||
case 72624: // Ooze Eruption
|
||||
case 72625: // Ooze Eruption
|
||||
// ONLY SPELLS WITH SPELLFAMILY_GENERIC and EFFECT_SCHOOL_DAMAGE, OR WEAPON_DMG_X
|
||||
case 66809: // Meteor Fists
|
||||
case 67331: // Meteor Fists
|
||||
case 66765: // Meteor Fists
|
||||
case 67333: // Meteor Fists
|
||||
spellInfo->AttributesCu |= SPELL_ATTR0_CU_SHARE_DAMAGE;
|
||||
break;
|
||||
case 18500: // Wing Buffet
|
||||
case 33086: // Wild Bite
|
||||
case 49749: // Piercing Blow
|
||||
case 52890: // Penetrating Strike
|
||||
case 53454: // Impale
|
||||
case 59446: // Impale
|
||||
case 62383: // Shatter
|
||||
case 64777: // Machine Gun
|
||||
case 65239: // Machine Gun
|
||||
case 69293: // Wing Buffet
|
||||
case 74439: // Machine Gun
|
||||
// Trial of the Crusader, Jaraxxus, Shivan Slash
|
||||
case 66378:
|
||||
case 67097:
|
||||
case 67098:
|
||||
case 67099:
|
||||
// Trial of the Crusader, Anub'arak, Impale
|
||||
case 65919:
|
||||
case 67858:
|
||||
case 67859:
|
||||
case 67860:
|
||||
case 63278: // Mark of the Faceless (General Vezax)
|
||||
case 64125: // Ulduar, Yogg-Saron, Squeeze
|
||||
case 64126: // Ulduar, Yogg-Saron, Squeeze
|
||||
case 62544: // Thrust (Argent Tournament)
|
||||
case 64588: // Thrust (Argent Tournament)
|
||||
case 66479: // Thrust (Argent Tournament)
|
||||
case 68505: // Thrust (Argent Tournament)
|
||||
case 62709: // Counterattack! (Argent Tournament)
|
||||
case 62626: // Break-Shield (Argent Tournament, Player)
|
||||
case 64590: // Break-Shield (Argent Tournament, Player)
|
||||
case 64342: // Break-Shield (Argent Tournament, NPC)
|
||||
case 64686: // Break-Shield (Argent Tournament, NPC)
|
||||
case 65147: // Break-Shield (Argent Tournament, NPC)
|
||||
case 68504: // Break-Shield (Argent Tournament, NPC)
|
||||
case 62874: // Charge (Argent Tournament, Player)
|
||||
case 68498: // Charge (Argent Tournament, Player)
|
||||
case 64591: // Charge (Argent Tournament, Player)
|
||||
case 63003: // Charge (Argent Tournament, NPC)
|
||||
case 63010: // Charge (Argent Tournament, NPC)
|
||||
case 68321: // Charge (Argent Tournament, NPC)
|
||||
case 72255: // Mark of the Fallen Champion (Deathbringer Saurfang)
|
||||
case 72444: // Mark of the Fallen Champion (Deathbringer Saurfang)
|
||||
case 72445: // Mark of the Fallen Champion (Deathbringer Saurfang)
|
||||
case 72446: // Mark of the Fallen Champion (Deathbringer Saurfang)
|
||||
case 72409: // Rune of Blood (Deathbringer Saurfang)
|
||||
case 72447: // Rune of Blood (Deathbringer Saurfang)
|
||||
case 72448: // Rune of Blood (Deathbringer Saurfang)
|
||||
case 72449: // Rune of Blood (Deathbringer Saurfang)
|
||||
case 49882: // Leviroth Self-Impale
|
||||
case 62775: // Ulduar: XT-002 Tympanic Tamparum
|
||||
spellInfo->AttributesCu |= SPELL_ATTR0_CU_IGNORE_ARMOR;
|
||||
break;
|
||||
case 64422: // Sonic Screech (Auriaya)
|
||||
spellInfo->AttributesCu |= SPELL_ATTR0_CU_SHARE_DAMAGE;
|
||||
spellInfo->AttributesCu |= SPELL_ATTR0_CU_IGNORE_ARMOR;
|
||||
break;
|
||||
case 72293: // Mark of the Fallen Champion (Deathbringer Saurfang)
|
||||
case 72347: // Lock Players and Tap Chest (Gunship Battle)
|
||||
spellInfo->AttributesCu |= SPELL_ATTR0_CU_NEGATIVE_EFF0;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
case 63675: // Improved Devouring Plague
|
||||
case 17962: // Conflagrate
|
||||
case 32593: // Earth Shield aura
|
||||
case 32594: // Earth Shield aura
|
||||
case 49283: // Earth Shield aura
|
||||
case 49284: // Earth Shield aura
|
||||
case 50526: // Wandering Plague
|
||||
case 53353: // Chimera Shot - Serpent trigger
|
||||
case 52752: // Ancestral Awakening Heal
|
||||
spellInfo->AttributesCu |= SPELL_ATTR0_CU_NO_POSITIVE_TAKEN_BONUS;
|
||||
break;
|
||||
case 65280: // Ulduar, Hodir, Singed
|
||||
case 28969: // Naxxramas, Crypt Guard, Acid Spit (10 normal)
|
||||
case 56098: // Naxxramas, Crypt Guard, Acid Spit (25 normal)
|
||||
case 27891: // Naxxramas, Sludge Belcher, Acidic Sludge (10 normal)
|
||||
case 54331: // Naxxramas, Sludge Belcher, Acidic Sludge (25 normal)
|
||||
case 29325: // Naxxramas, Stoneskin Gargoyle, Acid Volley (10 normal)
|
||||
case 54714: // Naxxramas, Stoneskin Gargoyle, Acid Volley (25 normal)
|
||||
case 65775: // Anub'arak, Swarm Scarab, Acid-Drenched Mandibles (10 normal)
|
||||
case 67861: // Anub'arak, Swarm Scarab, Acid-Drenched Mandibles (25 normal)
|
||||
case 67862: // Anub'arak, Swarm Scarab, Acid-Drenched Mandibles (10 heroic)
|
||||
case 67863: // Anub'arak, Swarm Scarab, Acid-Drenched Mandibles (25 heroic)
|
||||
case 55604: // Naxxramas, Unrelenting Trainee, Death Plague (10 normal)
|
||||
case 55645: // Naxxramas, Unrelenting Trainee, Death Plague (25 normal)
|
||||
case 67721: // Anub'arak, Nerubian Burrower, Expose Weakness (normal)
|
||||
case 67847: // Anub'arak, Nerubian Burrower, Expose Weakness (heroic)
|
||||
case 64638: // Ulduar, Winter Jormungar, Acidic Bite
|
||||
case 71157: // Icecrown Citadel, Plagued Zombie, Infected Wound
|
||||
case 72963: // Icecrown Citadel, Rot Worm, Flesh Rot (10 normal)
|
||||
case 72964: // Icecrown Citadel, Rot Worm, Flesh Rot (25 normal)
|
||||
case 72965: // Icecrown Citadel, Rot Worm, Flesh Rot (10 heroic)
|
||||
case 72966: // Icecrown Citadel, Rot Worm, Flesh Rot (25 heroic)
|
||||
case 72465: // Icecrown Citadel, Sindragosa, Respite for a Tormented Soul (weekly quest)
|
||||
case 45271: // Sunwell, Eredar Twins encounter, Dark Strike
|
||||
case 45347: // Sunwell, Eredar Twins encounter, Dark Touched
|
||||
case 45348: // Sunwell, Eredar Twins encounter, Flame Touched
|
||||
case 35859: // The Eye, Nether Vapor
|
||||
case 40520: // Black Temple, Shade Soul Channel
|
||||
case 40327: // Black Temple, Atrophy
|
||||
case 38449: // Serpentshrine Cavern, Blessing of the Tides
|
||||
case 38044: // Serpentshrine Cavern, Surge
|
||||
case 74507: // Ruby Sanctum, Siphoned Might
|
||||
case 49381: // Drak'tharon Keep, Consume
|
||||
case 59805: // Drak'tharon Keep, Consume
|
||||
case 55093: // Gundrak, Grip of Slad'ran
|
||||
case 30659: // Hellfire Ramparts, Fel Infusion
|
||||
case 54314: // Azjol'Nerub Drain Power
|
||||
case 59354: // Azjol'Nerub Drain Power
|
||||
case 34655: // Snake Trap, Deadly Poison
|
||||
case 11971: // Sunder Armor
|
||||
case 58567: // Player Sunder Armor
|
||||
case 12579: // Player Winter's Chill
|
||||
case 29306: // Naxxramas(Gluth's Zombies): Infected Wound
|
||||
case 61920: // Ulduar(Spellbreaker): Supercharge
|
||||
case 63978: // Ulduar(Rubble): Stone Nova
|
||||
case 15502: // Sunder Armor
|
||||
spellInfo->AttributesCu |= SPELL_ATTR0_CU_SINGLE_AURA_STACK;
|
||||
break;
|
||||
case 43138: // North Fleet Reservist Kill Credit
|
||||
spellInfo->AttributesCu |= SPELL_ATTR0_CU_ALLOW_INFLIGHT_TARGET;
|
||||
break;
|
||||
case 6197: // Eagle Eye
|
||||
spellInfo->AttributesCu |= SPELL_ATTR0_CU_NO_INITIAL_THREAT;
|
||||
break;
|
||||
case 50315: // Disco Ball
|
||||
spellInfo->AttributesCu |= SPELL_ATTR0_CU_NO_PVP_FLAG;
|
||||
break;
|
||||
case 14183: // Premeditation
|
||||
spellInfo->AttributesCu |= SPELL_ATTR0_CU_DONT_BREAK_STEALTH;
|
||||
break;
|
||||
|
||||
// Xinef: NOT CUSTOM, cant add in DBC CORRECTION because i need to swap effects, too much work to do there
|
||||
// Envenom
|
||||
case 32645:
|
||||
@@ -3796,8 +3534,7 @@ void SpellMgr::LoadSpellInfoCustomAttributes()
|
||||
case 44535: // Spirit Heal, abilities also have no cost
|
||||
spellInfo->Effects[EFFECT_0].MiscValue = 127;
|
||||
break;
|
||||
case 45537: // Cosmetic - Lightning Beam Channel
|
||||
spellInfo->AttributesCu |= SPELL_ATTR0_CU_IGNORE_EVADE;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,93 @@
|
||||
/*
|
||||
* 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/>.
|
||||
*/
|
||||
|
||||
#ifndef _ASYNC_TASK_H
|
||||
#define _ASYNC_TASK_H
|
||||
|
||||
#include "Errors.h"
|
||||
#include "Log.h"
|
||||
#include <chrono>
|
||||
#include <functional>
|
||||
#include <future>
|
||||
|
||||
template <typename T>
|
||||
class AsyncTask
|
||||
{
|
||||
public:
|
||||
using AsyncFunction = std::function<T()>;
|
||||
using CallbackFunction = std::function<void(T)>;
|
||||
|
||||
AsyncTask(AsyncFunction asyncFunc, CallbackFunction callbackFunc)
|
||||
: asyncFunc(std::move(asyncFunc)), callbackFunc(std::move(callbackFunc)), isReady(false)
|
||||
{
|
||||
}
|
||||
|
||||
~AsyncTask()
|
||||
{
|
||||
// Ensure that the asynchronous task has completed before destruction
|
||||
if (asyncTask.valid() && asyncTask.wait_for(std::chrono::seconds(0)) != std::future_status::ready)
|
||||
{
|
||||
asyncTask.wait(); // Wait for the task to complete
|
||||
}
|
||||
}
|
||||
|
||||
bool InvokeIfReady()
|
||||
{
|
||||
if (!isReady)
|
||||
{
|
||||
// Check if the asynchronous task is ready
|
||||
if (asyncTask.valid() && asyncTask.wait_for(std::chrono::seconds(0)) == std::future_status::ready)
|
||||
{
|
||||
// get() rethrows anything the async function threw. Swallowing it
|
||||
// would wedge the cluster handoff (the completion callback signals
|
||||
// map readiness to the registry), so fail fast with context and let
|
||||
// the registry's crash recovery rebalance this node.
|
||||
try
|
||||
{
|
||||
callbackFunc(asyncTask.get());
|
||||
}
|
||||
catch (std::exception const& e)
|
||||
{
|
||||
LOG_ERROR("server.tc9", "AsyncTask failed: {}", e.what());
|
||||
ABORT("AsyncTask failed: {}", e.what());
|
||||
}
|
||||
isReady = true;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void ExecuteAsync()
|
||||
{
|
||||
// Capture the function by value so a moved AsyncTask does not leave
|
||||
// the in-flight async holding a dangling this pointer.
|
||||
AsyncFunction fn = asyncFunc;
|
||||
asyncTask = std::async(std::launch::async, [fn = std::move(fn)]() mutable
|
||||
{
|
||||
return fn();
|
||||
});
|
||||
}
|
||||
|
||||
private:
|
||||
AsyncFunction asyncFunc;
|
||||
CallbackFunction callbackFunc;
|
||||
std::shared_future<T> asyncTask;
|
||||
bool isReady;
|
||||
};
|
||||
|
||||
#endif // _ASYNC_TASK_H
|
||||
@@ -0,0 +1,115 @@
|
||||
/*
|
||||
* 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 "TC9GroupHooks.h"
|
||||
#include "CharacterCache.h"
|
||||
#include "Group.h"
|
||||
#include "GroupMgr.h"
|
||||
#include "Log.h"
|
||||
|
||||
void ToCloud9GroupHooks::OnGroupCreated(EventObjectGroup *group)
|
||||
{
|
||||
LOG_INFO("server", "Group created. ID: {}; Leader: {}.", group->guid, group->leader);
|
||||
|
||||
// Idempotent under sidecar event redelivery: a replayed create must not leak a second Group.
|
||||
if (sGroupMgr->GetGroupByGUID(group->guid))
|
||||
return;
|
||||
|
||||
Group* g = new Group();
|
||||
g->m_guid = ObjectGuid(HighGuid::Group, group->guid);
|
||||
g->m_leaderGuid = ObjectGuid(group->leader);
|
||||
sCharacterCache->GetCharacterNameByGuid(g->m_leaderGuid, g->m_leaderName);
|
||||
g->m_dungeonDifficulty = Difficulty(group->difficulty);
|
||||
g->m_raidDifficulty = Difficulty(group->raidDifficulty);
|
||||
g->m_lootMethod = LootMethod(group->lootMethod);
|
||||
g->m_lootThreshold = ItemQualities(group->lootThreshold);
|
||||
g->m_looterGuid = ObjectGuid(group->looterGuid);
|
||||
g->m_masterLooterGuid = ObjectGuid(group->masterLooterGuid);
|
||||
g->m_groupType = GroupType(group->groupType);
|
||||
|
||||
// Must precede member insertion: it zeroes the subgroup counters that AddMemberWithGuid increments.
|
||||
if (g->m_groupType & GROUPTYPE_RAID)
|
||||
g->_initRaidSubGroupsCounter();
|
||||
|
||||
for (int i = 0; i < group->membersSize; i++)
|
||||
g->AddMemberWithGuid(ObjectGuid(group->members[i]));
|
||||
|
||||
sGroupMgr->AddGroup(g);
|
||||
// Mark the service-assigned id used so a locally generated group can't collide with it.
|
||||
sGroupMgr->RegisterGroupId(g->GetGUID().GetCounter());
|
||||
}
|
||||
|
||||
void ToCloud9GroupHooks::OnGroupDisbanded(uint32 group)
|
||||
{
|
||||
LOG_INFO("server", "Group disbanded. ID: {}.", group);
|
||||
|
||||
if (Group* g = sGroupMgr->GetGroupByGUID(group))
|
||||
g->ForcedDisband(true);
|
||||
}
|
||||
|
||||
void ToCloud9GroupHooks::OnGroupMemberAdded(uint32 group, uint64 member)
|
||||
{
|
||||
LOG_INFO("server", "Group member added. ID: {}; Member: {}.", group, member);
|
||||
|
||||
if (Group* g = sGroupMgr->GetGroupByGUID(group))
|
||||
g->AddMemberWithGuid(ObjectGuid(member));
|
||||
}
|
||||
|
||||
void ToCloud9GroupHooks::OnGroupMemberRemoved(uint32 group, uint64 member, uint64 newLeader)
|
||||
{
|
||||
LOG_INFO("server", "Group member removed. ID: {}; Member: {}; NewLeader: {}.", group, member, newLeader);
|
||||
|
||||
if (Group* g = sGroupMgr->GetGroupByGUID(group))
|
||||
g->RemoveMember(ObjectGuid(member));
|
||||
}
|
||||
|
||||
void ToCloud9GroupHooks::OnGroupLootTypeChanged(uint32 group, uint8 lootType, uint64 looter, uint8 lootThreshold)
|
||||
{
|
||||
LOG_INFO("server", "Group loot type changed. ID: {}; LootType: {}; Looter: {}; LootThreshold: {}.",
|
||||
group, lootType, looter, lootThreshold);
|
||||
|
||||
if (Group* g = sGroupMgr->GetGroupByGUID(group))
|
||||
{
|
||||
g->SetLootMethod((LootMethod)lootType);
|
||||
g->SetMasterLooterGuid(ObjectGuid(looter));
|
||||
g->SetLootThreshold((ItemQualities)lootThreshold);
|
||||
}
|
||||
}
|
||||
|
||||
void ToCloud9GroupHooks::OnGroupConvertedToRaid(uint32 group)
|
||||
{
|
||||
LOG_INFO("server", "Group converted to raid. ID: {}.", group);
|
||||
|
||||
if (Group* g = sGroupMgr->GetGroupByGUID(group))
|
||||
g->ConvertToRaid();
|
||||
}
|
||||
|
||||
void ToCloud9GroupHooks::OnGroupRaidDifficultyChanged(uint32 group, uint8 difficulty)
|
||||
{
|
||||
LOG_INFO("server", "Raid difficulty changed. ID: {}; Difficulty: {}.", group, difficulty);
|
||||
|
||||
if (Group* g = sGroupMgr->GetGroupByGUID(group))
|
||||
g->SetRaidDifficulty((Difficulty)difficulty);
|
||||
}
|
||||
|
||||
void ToCloud9GroupHooks::OnGroupDungeonDifficultyChanged(uint32 group, uint8 difficulty)
|
||||
{
|
||||
LOG_INFO("server", "Dungeon difficulty changed. ID: {}; Difficulty: {}.", group, difficulty);
|
||||
|
||||
if (Group* g = sGroupMgr->GetGroupByGUID(group))
|
||||
g->SetDungeonDifficulty((Difficulty)difficulty);
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
/*
|
||||
* 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/>.
|
||||
*/
|
||||
|
||||
#ifndef _TC9_GROUP_HOOKS_H
|
||||
#define _TC9_GROUP_HOOKS_H
|
||||
|
||||
#include "Common.h"
|
||||
#include "libsidecar.h"
|
||||
|
||||
class ToCloud9GroupHooks
|
||||
{
|
||||
public:
|
||||
ToCloud9GroupHooks() {};
|
||||
~ToCloud9GroupHooks() {};
|
||||
|
||||
static void OnGroupCreated(EventObjectGroup *group);
|
||||
static void OnGroupDisbanded(uint32 group);
|
||||
static void OnGroupMemberAdded(uint32 group, uint64 member);
|
||||
static void OnGroupMemberRemoved(uint32 group, uint64 member, uint64 newLeader);
|
||||
static void OnGroupLootTypeChanged(uint32 group, uint8 lootType, uint64 looter, uint8 lootThreshold);
|
||||
static void OnGroupConvertedToRaid(uint32 group);
|
||||
static void OnGroupRaidDifficultyChanged(uint32 group, uint8 difficulty);
|
||||
static void OnGroupDungeonDifficultyChanged(uint32 group, uint8 difficulty);
|
||||
};
|
||||
|
||||
#endif /* TC9GroupHooks_h */
|
||||
@@ -0,0 +1,374 @@
|
||||
/*
|
||||
* 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 "TC9GrpcHandler.h"
|
||||
#include "Bag.h"
|
||||
#include "BattlegroundMgr.h"
|
||||
#include "Item.h"
|
||||
#include "ObjectAccessor.h"
|
||||
#include "Player.h"
|
||||
|
||||
GetPlayerItemsByGuidsResponse ToCloud9GrpcHandler::GetPlayerItemsByGuids(uint64 playerGuid, uint64* items, int itemsLen)
|
||||
{
|
||||
Player *player = ObjectAccessor::FindPlayer(ObjectGuid(playerGuid));
|
||||
if (!player)
|
||||
{
|
||||
GetPlayerItemsByGuidsResponse resp;
|
||||
resp.errorCode = PlayerItemErrorCodePlayerNotFound;
|
||||
return resp;
|
||||
}
|
||||
|
||||
if (itemsLen <= 0)
|
||||
{
|
||||
GetPlayerItemsByGuidsResponse resp;
|
||||
resp.errorCode = PlayerItemErrorCodeNoError;
|
||||
resp.items = nullptr;
|
||||
resp.itemsSize = 0;
|
||||
return resp;
|
||||
}
|
||||
|
||||
int itemsFound = 0;
|
||||
std::unique_ptr<Item* []> foundItems(new Item * [itemsLen]);
|
||||
for (int i = 0; i < itemsLen; i++)
|
||||
{
|
||||
foundItems[i] = player->GetItemByGuid(ObjectGuid(items[i]));
|
||||
if (foundItems[i])
|
||||
itemsFound++;
|
||||
}
|
||||
|
||||
// Don't forget to delete on "that" side.
|
||||
PlayerItem* itemsResult = static_cast<PlayerItem *>(malloc(sizeof(PlayerItem) * itemsFound));
|
||||
int itemsResultsItr = 0;
|
||||
for (int i = 0; i < itemsLen; i++)
|
||||
{
|
||||
if (!foundItems[i])
|
||||
continue;
|
||||
|
||||
Item* pItem = foundItems[i];
|
||||
|
||||
PlayerItem item;
|
||||
item.guid = pItem->GetGUID().GetRawValue();
|
||||
item.entry = pItem->GetEntry();
|
||||
item.owner = playerGuid;
|
||||
item.bagSlot = pItem->GetBagSlot();
|
||||
item.slot = pItem->GetSlot();
|
||||
item.isTradable = pItem->CanBeTraded(true);
|
||||
item.count = pItem->GetCount();
|
||||
item.flags = pItem->GetUInt32Value(ITEM_FIELD_FLAGS);
|
||||
item.durability = pItem->GetUInt32Value(ITEM_FIELD_DURABILITY);
|
||||
item.randomPropertyID = pItem->GetItemRandomPropertyId();
|
||||
|
||||
// Don't forget to delete on "that" side.
|
||||
char *text = (char*)malloc(sizeof(char) * (pItem->GetText().length() + 1));
|
||||
strcpy(text, pItem->GetText().c_str());
|
||||
item.text = text;
|
||||
|
||||
itemsResult[itemsResultsItr] = item;
|
||||
|
||||
itemsResultsItr++;
|
||||
}
|
||||
|
||||
GetPlayerItemsByGuidsResponse resp;
|
||||
resp.errorCode = PlayerItemErrorCodeNoError;
|
||||
resp.items = itemsResult;
|
||||
resp.itemsSize = itemsFound;
|
||||
return resp;
|
||||
}
|
||||
|
||||
RemoveItemsWithGuidsFromPlayerResponse ToCloud9GrpcHandler::RemoveItemsWithGuidsFromPlayer(uint64 playerGuid, uint64* items, int itemsLen, uint64 assignToPlayerGuid)
|
||||
{
|
||||
Player *player = ObjectAccessor::FindPlayer(ObjectGuid(playerGuid));
|
||||
if (!player)
|
||||
{
|
||||
RemoveItemsWithGuidsFromPlayerResponse resp;
|
||||
resp.errorCode = PlayerItemErrorCodePlayerNotFound;
|
||||
return resp;
|
||||
}
|
||||
|
||||
if (itemsLen <= 0)
|
||||
{
|
||||
RemoveItemsWithGuidsFromPlayerResponse resp;
|
||||
resp.errorCode = PlayerItemErrorCodeNoError;
|
||||
resp.updatedItems = nullptr;
|
||||
resp.updatedItemsSize = 0;
|
||||
return resp;
|
||||
}
|
||||
|
||||
CharacterDatabaseTransaction trans = CharacterDatabase.BeginTransaction();
|
||||
|
||||
int itemsFound = 0;
|
||||
std::unique_ptr<uint64[]> deletedItems(new uint64 [itemsLen]);
|
||||
for (int i = 0; i < itemsLen; i++)
|
||||
{
|
||||
Item *item = player->GetItemByGuid(ObjectGuid(items[i]));
|
||||
if (!item)
|
||||
{
|
||||
deletedItems[i] = 0;
|
||||
continue;
|
||||
}
|
||||
|
||||
itemsFound++;
|
||||
deletedItems[i] = item->GetGUID().GetRawValue();
|
||||
|
||||
item->SetNotRefundable(player);
|
||||
player->MoveItemFromInventory(item->GetBagSlot(), item->GetSlot(), true);
|
||||
|
||||
item->DeleteFromInventoryDB(trans);
|
||||
item->SetOwnerGUID(ObjectGuid(assignToPlayerGuid));
|
||||
item->SetState(ITEM_CHANGED);
|
||||
item->SaveToDB(trans);
|
||||
|
||||
delete item;
|
||||
}
|
||||
|
||||
if (itemsFound > 0)
|
||||
{
|
||||
player->SaveInventoryAndGoldToDB(trans);
|
||||
CharacterDatabase.CommitTransaction(trans);
|
||||
}
|
||||
|
||||
// Don't forget to delete on "that" side.
|
||||
uint64_t* itemsResult = (uint64_t*)malloc(sizeof(uint64_t) * itemsFound);
|
||||
int itemsResultsItr = 0;
|
||||
for (int i = 0; i < itemsLen; i++)
|
||||
{
|
||||
if (deletedItems[i] == 0)
|
||||
continue;
|
||||
|
||||
itemsResult[itemsResultsItr] = deletedItems[i];
|
||||
itemsResultsItr++;
|
||||
}
|
||||
|
||||
RemoveItemsWithGuidsFromPlayerResponse resp;
|
||||
resp.errorCode = PlayerItemErrorCodeNoError;
|
||||
resp.updatedItems = itemsResult;
|
||||
resp.updatedItemsSize = itemsResultsItr;
|
||||
return resp;
|
||||
|
||||
}
|
||||
|
||||
PlayerItemErrorCode ToCloud9GrpcHandler::AddExistingItemToPlayer(AddExistingItemToPlayerRequest* request)
|
||||
{
|
||||
Player *player = ObjectAccessor::FindPlayer(ObjectGuid(request->playerGuid));
|
||||
if (!player)
|
||||
return PlayerItemErrorCodePlayerNotFound;
|
||||
|
||||
ItemTemplate const* proto = sObjectMgr->GetItemTemplate(request->itemEntry);
|
||||
if (!proto)
|
||||
return PlayerItemErrorUnknownTemplate;
|
||||
|
||||
Item* item = NewItemOrBag(proto);
|
||||
if (!item->Create(ObjectGuid(request->itemGuid).GetCounter(), request->itemEntry, player))
|
||||
{
|
||||
delete item;
|
||||
return PlayerItemErrorFailedToCreateItem;
|
||||
}
|
||||
|
||||
item->SetUInt32Value(ITEM_FIELD_FLAGS, request->itemFlags);
|
||||
item->SetUInt32Value(ITEM_FIELD_DURABILITY, request->itemDurability);
|
||||
item->SetItemRandomProperties(request->itemRandomPropertyID);
|
||||
item->SetCount(request->itemCount);
|
||||
|
||||
// TODO: Add text.
|
||||
|
||||
ItemPosCountVec dest;
|
||||
uint8 msg = player->CanStoreItem(NULL_BAG, NULL_SLOT, dest, item, false);
|
||||
if (msg != EQUIP_ERR_OK)
|
||||
{
|
||||
delete item;
|
||||
return PlayerItemErrorNoInventorySpace;
|
||||
}
|
||||
|
||||
player->MoveItemToInventory(dest, item, true);
|
||||
|
||||
CharacterDatabaseTransaction trans = CharacterDatabase.BeginTransaction();
|
||||
player->SaveInventoryAndGoldToDB(trans);
|
||||
CharacterDatabase.CommitTransaction(trans);
|
||||
|
||||
return PlayerItemErrorCodeNoError;
|
||||
}
|
||||
|
||||
GetMoneyForPlayerResponse ToCloud9GrpcHandler::GetMoneyForPlayer(uint64 playerGuid)
|
||||
{
|
||||
Player *player = ObjectAccessor::FindPlayer(ObjectGuid(playerGuid));
|
||||
if (!player)
|
||||
{
|
||||
GetMoneyForPlayerResponse resp;
|
||||
resp.errorCode = PlayerMoneyErrorCodePlayerNotFound;
|
||||
return resp;
|
||||
}
|
||||
|
||||
GetMoneyForPlayerResponse resp;
|
||||
resp.errorCode = PlayerMoneyErrorCodeNoError;
|
||||
resp.money = player->GetMoney();
|
||||
return resp;
|
||||
}
|
||||
|
||||
ModifyMoneyForPlayerResponse ToCloud9GrpcHandler::ModifyMoneyForPlayer(uint64 playerGuid, int32 value)
|
||||
{
|
||||
Player *player = ObjectAccessor::FindPlayer(ObjectGuid(playerGuid));
|
||||
if (!player)
|
||||
{
|
||||
ModifyMoneyForPlayerResponse resp;
|
||||
resp.errorCode = PlayerMoneyErrorCodePlayerNotFound;
|
||||
return resp;
|
||||
}
|
||||
|
||||
if (!player->ModifyMoney(value, true))
|
||||
{
|
||||
ModifyMoneyForPlayerResponse resp;
|
||||
resp.errorCode = PlayerMoneyErrorCodeTooMuchMoney;
|
||||
resp.newMoneyValue = player->GetMoney();
|
||||
return resp;
|
||||
}
|
||||
|
||||
ModifyMoneyForPlayerResponse resp;
|
||||
resp.errorCode = PlayerMoneyErrorCodeNoError;
|
||||
resp.newMoneyValue = player->GetMoney();
|
||||
return resp;
|
||||
}
|
||||
|
||||
CanPlayerInteractWithGOAndTypeResponse ToCloud9GrpcHandler::CanPlayerInteractWithGOAndType(uint64 playerGuid, uint64 go, uint8 goType)
|
||||
{
|
||||
Player *player = ObjectAccessor::FindPlayer(ObjectGuid(playerGuid));
|
||||
if (!player)
|
||||
{
|
||||
CanPlayerInteractWithGOAndTypeResponse resp;
|
||||
resp.errorCode = PlayerInteractionErrorCodeCodePlayerNotFound;
|
||||
return resp;
|
||||
}
|
||||
|
||||
CanPlayerInteractWithGOAndTypeResponse resp;
|
||||
resp.errorCode = PlayerInteractionErrorCodeNoError;
|
||||
resp.canInteract = player->GetGameObjectIfCanInteractWith(ObjectGuid(go), (GameobjectTypes)goType) != nullptr;
|
||||
return resp;
|
||||
}
|
||||
|
||||
CanPlayerInteractWithNPCAndFlagsResponse ToCloud9GrpcHandler::CanPlayerInteractWithNPCAndFlags(uint64 playerGuid, uint64 npc, uint32 unitFlags)
|
||||
{
|
||||
Player *player = ObjectAccessor::FindPlayer(ObjectGuid(playerGuid));
|
||||
if (!player)
|
||||
{
|
||||
CanPlayerInteractWithNPCAndFlagsResponse resp;
|
||||
resp.errorCode = PlayerInteractionErrorCodeCodePlayerNotFound;
|
||||
return resp;
|
||||
}
|
||||
|
||||
CanPlayerInteractWithNPCAndFlagsResponse resp;
|
||||
resp.errorCode = PlayerInteractionErrorCodeNoError;
|
||||
resp.canInteract = player->GetNPCIfCanInteractWith(ObjectGuid(npc), (NPCFlags)unitFlags) != nullptr;
|
||||
return resp;
|
||||
}
|
||||
|
||||
BattlegroundStartResponse ToCloud9GrpcHandler::StartBattleground(BattlegroundStartRequest* req)
|
||||
{
|
||||
PvPDifficultyEntry const* pvpEntry = GetBattlegroundBracketByLevel(req->mapID, req->bracketLvl);
|
||||
if (!pvpEntry)
|
||||
{
|
||||
BattlegroundStartResponse resp;
|
||||
resp.errorCode = BattlegroundErrorFailedToCreateBG;
|
||||
return resp;
|
||||
}
|
||||
|
||||
BattlegroundTypeId bgTypeId = BattlegroundTypeId(req->battlegroundTypeID);
|
||||
|
||||
Battleground* bg = sBattlegroundMgr->CreateNewBattleground(bgTypeId, pvpEntry, req->arenaType, req->isRated);
|
||||
if (!bg)
|
||||
{
|
||||
BattlegroundStartResponse resp;
|
||||
resp.errorCode = BattlegroundErrorFailedToCreateBG;
|
||||
return resp;
|
||||
}
|
||||
|
||||
bg->StartBattleground();
|
||||
|
||||
bg->IncreaseInvitedCount(TEAM_HORDE);
|
||||
bg->IncreaseInvitedCount(TEAM_ALLIANCE);
|
||||
|
||||
BattlegroundStartResponse resp;
|
||||
resp.errorCode = BattlegroundErrorCodeNoError;
|
||||
resp.instanceID = bg->GetInstanceID();
|
||||
resp.instanceClientID = bg->GetClientInstanceID();
|
||||
return resp;
|
||||
}
|
||||
|
||||
BattlegroundErrorCode ToCloud9GrpcHandler::AddPlayersToBattleground(BattlegroundAddPlayersRequest* request)
|
||||
{
|
||||
BattlegroundTypeId bgTypeId = BattlegroundTypeId(request->battlegroundTypeID);
|
||||
|
||||
Battleground* bg = sBattlegroundMgr->GetBattleground(request->instanceID, BATTLEGROUND_TYPE_NONE);
|
||||
if (!bg)
|
||||
return BattlegroundErrorBattlegroundNotFound;
|
||||
|
||||
for (int i = 0; i < request->alliancePlayersToAddSize; i++)
|
||||
{
|
||||
Player *player = ObjectAccessor::FindPlayer(ObjectGuid(request->alliancePlayersToAdd[i]));
|
||||
if (player)
|
||||
{
|
||||
player->SetEntryPoint();
|
||||
player->SetBattlegroundId(bg->GetInstanceID(), bg->GetBgTypeID(), 1, true, bgTypeId == BATTLEGROUND_RB, player->GetTeamId(true));
|
||||
sBattlegroundMgr->SendToBattleground(player, bg->GetInstanceID(), bgTypeId);
|
||||
}
|
||||
}
|
||||
|
||||
for (int i = 0; i < request->hordePlayersToAddSize; i++)
|
||||
{
|
||||
Player *player = ObjectAccessor::FindPlayer(ObjectGuid(request->hordePlayersToAdd[i]));
|
||||
if (player)
|
||||
{
|
||||
player->SetEntryPoint();
|
||||
player->SetBattlegroundId(bg->GetInstanceID(), bg->GetBgTypeID(), 1, true, bgTypeId == BATTLEGROUND_RB, player->GetTeamId(true));
|
||||
sBattlegroundMgr->SendToBattleground(player, bg->GetInstanceID(), bgTypeId);
|
||||
}
|
||||
}
|
||||
|
||||
return BattlegroundErrorCodeNoError;
|
||||
}
|
||||
|
||||
BattlegroundJoinCheckErrorCode ToCloud9GrpcHandler::CanPlayerJoinBattlegroundQueue(uint64 playerGuid)
|
||||
{
|
||||
Player *player = ObjectAccessor::FindPlayer(ObjectGuid(playerGuid));
|
||||
if (!player)
|
||||
return BattlegroundJoinCheckErrorCodePlayerNotFound;
|
||||
|
||||
// Lets ignore RBAC checks for now.
|
||||
Battleground* bg = sBattlegroundMgr->GetBattlegroundTemplate(BATTLEGROUND_RB);
|
||||
if (!bg)
|
||||
return BattlegroundJoinCheckErrorCodeResponseIsFalse;
|
||||
|
||||
// has deserter debuff
|
||||
if (!player->CanJoinToBattleground(bg))
|
||||
return BattlegroundJoinCheckErrorCodeResponseIsFalse;
|
||||
|
||||
// don't let Death Knights join BG queues when they are not allowed to be teleported yet
|
||||
if (player->IsClass(CLASS_DEATH_KNIGHT, CLASS_CONTEXT_TELEPORT) && player->GetMapId() == 609 && !player->IsGameMaster() && !player->HasSpell(50977))
|
||||
return BattlegroundJoinCheckErrorCodeResponseIsFalse;
|
||||
|
||||
return BattlegroundJoinCheckErrorCodeOK;
|
||||
}
|
||||
|
||||
BattlegroundJoinCheckErrorCode ToCloud9GrpcHandler::CanPlayerTeleportToBattleground(uint64 playerGuid)
|
||||
{
|
||||
Player *player = ObjectAccessor::FindPlayer(ObjectGuid(playerGuid));
|
||||
if (!player)
|
||||
return BattlegroundJoinCheckErrorCodePlayerNotFound;
|
||||
|
||||
if (player->GetCharmGUID() || player->IsInCombat())
|
||||
return BattlegroundJoinCheckErrorCodeResponseIsFalse;
|
||||
|
||||
return BattlegroundJoinCheckErrorCodeOK;
|
||||
}
|
||||
@@ -0,0 +1,50 @@
|
||||
/*
|
||||
* 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/>.
|
||||
*/
|
||||
|
||||
#ifndef _TC9_GRPC_HANDLER_H
|
||||
#define _TC9_GRPC_HANDLER_H
|
||||
|
||||
#include "Common.h"
|
||||
#include "libsidecar.h"
|
||||
|
||||
class ToCloud9GrpcHandler
|
||||
{
|
||||
public:
|
||||
ToCloud9GrpcHandler() {};
|
||||
~ToCloud9GrpcHandler() {};
|
||||
|
||||
// Items
|
||||
static GetPlayerItemsByGuidsResponse GetPlayerItemsByGuids(uint64 player, uint64* items, int items_len);
|
||||
static RemoveItemsWithGuidsFromPlayerResponse RemoveItemsWithGuidsFromPlayer(uint64 player, uint64* items, int itemsLen, uint64 assignToPlayer);
|
||||
static PlayerItemErrorCode AddExistingItemToPlayer(AddExistingItemToPlayerRequest*);
|
||||
|
||||
// Money
|
||||
static GetMoneyForPlayerResponse GetMoneyForPlayer(uint64 player);
|
||||
static ModifyMoneyForPlayerResponse ModifyMoneyForPlayer(uint64 player, int32 value);
|
||||
|
||||
// Interactions
|
||||
static CanPlayerInteractWithGOAndTypeResponse CanPlayerInteractWithGOAndType(uint64 player, uint64 go, uint8 goType);
|
||||
static CanPlayerInteractWithNPCAndFlagsResponse CanPlayerInteractWithNPCAndFlags(uint64 player, uint64 npc, uint32 unitFlags);
|
||||
|
||||
// Battlegrounds
|
||||
static BattlegroundStartResponse StartBattleground(BattlegroundStartRequest* request);
|
||||
static BattlegroundErrorCode AddPlayersToBattleground(BattlegroundAddPlayersRequest* request);
|
||||
static BattlegroundJoinCheckErrorCode CanPlayerJoinBattlegroundQueue(uint64 player);
|
||||
static BattlegroundJoinCheckErrorCode CanPlayerTeleportToBattleground(uint64 player);
|
||||
};
|
||||
|
||||
#endif // _TC9_GRPC_HANDLER_H
|
||||
@@ -0,0 +1,47 @@
|
||||
/*
|
||||
* 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 "TC9GuildHooks.h"
|
||||
#include "ObjectAccessor.h"
|
||||
#include "Player.h"
|
||||
|
||||
void ToCloud9GuildHooks::OnGuildMemberAdded(uint64 guild, uint64 character)
|
||||
{
|
||||
Player *player = ObjectAccessor::FindPlayer(ObjectGuid(character));
|
||||
if (!player)
|
||||
return;
|
||||
|
||||
player->SetInGuild(guild);
|
||||
}
|
||||
|
||||
void ToCloud9GuildHooks::OnGuildMemberRemoved(uint64 /*guild*/, uint64 character)
|
||||
{
|
||||
Player *player = ObjectAccessor::FindPlayer(ObjectGuid(character));
|
||||
if (!player)
|
||||
return;
|
||||
|
||||
player->SetInGuild(0);
|
||||
}
|
||||
|
||||
void ToCloud9GuildHooks::OnGuildMemberLeft(uint64 /*guild*/, uint64 character)
|
||||
{
|
||||
Player *player = ObjectAccessor::FindPlayer(ObjectGuid(character));
|
||||
if (!player)
|
||||
return;
|
||||
|
||||
player->SetInGuild(0);
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
/*
|
||||
* 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/>.
|
||||
*/
|
||||
|
||||
#ifndef _TC9_GUILD_HOOKS_H
|
||||
#define _TC9_GUILD_HOOKS_H
|
||||
|
||||
#include "Common.h"
|
||||
|
||||
class ToCloud9GuildHooks
|
||||
{
|
||||
public:
|
||||
ToCloud9GuildHooks() {};
|
||||
~ToCloud9GuildHooks() {};
|
||||
|
||||
static void OnGuildMemberAdded(uint64 guild, uint64 character);
|
||||
static void OnGuildMemberRemoved(uint64 guild, uint64 character);
|
||||
static void OnGuildMemberLeft(uint64 guild, uint64 character);
|
||||
};
|
||||
|
||||
#endif // _TC9_GUILD_HOOKS_H
|
||||
@@ -0,0 +1,268 @@
|
||||
/*
|
||||
* 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 "TC9Sidecar.h"
|
||||
#include "Config.h"
|
||||
#include "InstanceSaveMgr.h"
|
||||
#include "libsidecar.h"
|
||||
#include "Log.h"
|
||||
#include "MapMgr.h"
|
||||
#include "Player.h"
|
||||
#include "TC9GroupHooks.h"
|
||||
#include "TC9GrpcHandler.h"
|
||||
#include "TC9GuildHooks.h"
|
||||
#include "UpdateTime.h"
|
||||
#include "WorldSessionMgr.h"
|
||||
|
||||
#include <limits>
|
||||
|
||||
#define AVAILABLE_MAPS_ALL_MAPS ""
|
||||
|
||||
MonitoringDataCollectorResponse HandleMonitoringRequest();
|
||||
|
||||
ToCloud9Sidecar* ToCloud9Sidecar::instance()
|
||||
{
|
||||
static ToCloud9Sidecar instance;
|
||||
return &instance;
|
||||
}
|
||||
|
||||
ToCloud9Sidecar::ToCloud9Sidecar() : _clusterModeEnabled(false), _isCrossrealm(false)
|
||||
{
|
||||
}
|
||||
|
||||
void ToCloud9Sidecar::Init(uint16 port, int realmId)
|
||||
{
|
||||
_clusterModeEnabled = sConfigMgr->GetOption<bool>("Cluster.Enabled", false);
|
||||
|
||||
if (_clusterModeEnabled)
|
||||
{
|
||||
uint32 *assignedMaps;
|
||||
int assignedMapsSize = 0;
|
||||
|
||||
_isCrossrealm = sConfigMgr->GetOption<bool>("Cluster.IsCrossrealm", false);
|
||||
|
||||
std::string availableMaps = sConfigMgr->GetOption<std::string>("Cluster.AvailableMaps", AVAILABLE_MAPS_ALL_MAPS);
|
||||
TC9InitLib(port, realmId, _isCrossrealm, availableMaps.data(), &assignedMaps, &assignedMapsSize);
|
||||
|
||||
for (int i = 0; i < MAX_MAP_ID; i++)
|
||||
_assignedMapsByID[i] = false;
|
||||
|
||||
for (int i = 0; i < assignedMapsSize; i++)
|
||||
{
|
||||
uint32 mapId = assignedMaps[i];
|
||||
if (mapId >= MAX_MAP_ID)
|
||||
{
|
||||
LOG_ERROR("server", "ToCloud9Sidecar::Init: map id {} out of range [0, {}), ignored",
|
||||
mapId, MAX_MAP_ID);
|
||||
continue;
|
||||
}
|
||||
_assignedMapsByID[mapId] = true;
|
||||
}
|
||||
|
||||
if (assignedMapsSize > 0)
|
||||
free(assignedMaps);
|
||||
|
||||
SetupHooks();
|
||||
SetupGrpcHandlers();
|
||||
}
|
||||
}
|
||||
|
||||
void ToCloud9Sidecar::Deinit()
|
||||
{
|
||||
if (_clusterModeEnabled)
|
||||
TC9GracefulShutdown();
|
||||
}
|
||||
|
||||
void ToCloud9Sidecar::SetupHooks()
|
||||
{
|
||||
TC9SetOnMapsReassignedHook(&ToCloud9Sidecar::OnMapsReassigned);
|
||||
|
||||
TC9SetOnGuildMemberLeftHook(&ToCloud9GuildHooks::OnGuildMemberLeft);
|
||||
TC9SetOnGuildMemberAddedHook(&ToCloud9GuildHooks::OnGuildMemberAdded);
|
||||
TC9SetOnGuildMemberRemovedHook(&ToCloud9GuildHooks::OnGuildMemberRemoved);
|
||||
|
||||
TC9SetOnGroupCreatedHook(&ToCloud9GroupHooks::OnGroupCreated);
|
||||
TC9SetOnGroupDisbandedHook(&ToCloud9GroupHooks::OnGroupDisbanded);
|
||||
TC9SetOnGroupMemberAddedHook(&ToCloud9GroupHooks::OnGroupMemberAdded);
|
||||
TC9SetOnGroupMemberRemovedHook(&ToCloud9GroupHooks::OnGroupMemberRemoved);
|
||||
TC9SetOnGroupLootTypeChangedHook(&ToCloud9GroupHooks::OnGroupLootTypeChanged);
|
||||
TC9SetOnGroupConvertedToRaidHook(&ToCloud9GroupHooks::OnGroupConvertedToRaid);
|
||||
TC9SetOnGroupRaidDifficultyChangedHook(&ToCloud9GroupHooks::OnGroupRaidDifficultyChanged);
|
||||
TC9SetOnGroupDungeonDifficultyChangedHook(&ToCloud9GroupHooks::OnGroupDungeonDifficultyChanged);
|
||||
}
|
||||
|
||||
void ToCloud9Sidecar::SetupGrpcHandlers()
|
||||
{
|
||||
TC9SetGetPlayerItemsByGuidsHandler(&ToCloud9GrpcHandler::GetPlayerItemsByGuids);
|
||||
TC9SetRemoveItemsWithGuidsFromPlayerHandler(&ToCloud9GrpcHandler::RemoveItemsWithGuidsFromPlayer);
|
||||
TC9SetAddExistingItemToPlayerHandler(&ToCloud9GrpcHandler::AddExistingItemToPlayer);
|
||||
|
||||
TC9SetGetMoneyForPlayerHandler(&ToCloud9GrpcHandler::GetMoneyForPlayer);
|
||||
TC9SetModifyMoneyForPlayerHandler(&ToCloud9GrpcHandler::ModifyMoneyForPlayer);
|
||||
|
||||
TC9SetCanPlayerInteractWithGOAndTypeHandler(&ToCloud9GrpcHandler::CanPlayerInteractWithGOAndType);
|
||||
TC9SetCanPlayerInteractWithNPCAndFlagsHandler(&ToCloud9GrpcHandler::CanPlayerInteractWithNPCAndFlags);
|
||||
|
||||
TC9SetBattlegroundStartHandler(&ToCloud9GrpcHandler::StartBattleground);
|
||||
TC9SetBattlegroundAddPlayersHandler(&ToCloud9GrpcHandler::AddPlayersToBattleground);
|
||||
TC9SetCanPlayerJoinBattlegroundQueueHandler(&ToCloud9GrpcHandler::CanPlayerJoinBattlegroundQueue);
|
||||
TC9SetCanPlayerTeleportToBattlegroundHandler(&ToCloud9GrpcHandler::CanPlayerTeleportToBattleground);
|
||||
|
||||
TC9SetMonitoringDataCollectorHandler(&HandleMonitoringRequest);
|
||||
}
|
||||
|
||||
void ToCloud9Sidecar::ProcessHooks()
|
||||
{
|
||||
TC9ProcessEventsHooks();
|
||||
}
|
||||
|
||||
void ToCloud9Sidecar::ProcessGrpcOrHttpRequests()
|
||||
{
|
||||
TC9ProcessGRPCOrHTTPRequests();
|
||||
}
|
||||
|
||||
void ToCloud9Sidecar::ProcessAsyncTasks()
|
||||
{
|
||||
_asyncTasksProcessor.ProcessReadyCallbacks();
|
||||
}
|
||||
|
||||
bool ToCloud9Sidecar::IsMapAssigned(uint32 mapId)
|
||||
{
|
||||
if (mapId >= MAX_MAP_ID)
|
||||
return false;
|
||||
|
||||
return _assignedMapsByID[mapId];
|
||||
}
|
||||
|
||||
uint32 ToCloud9Sidecar::GenerateCharacterGuid(uint16 realmId)
|
||||
{
|
||||
return uint32(TC9GetNextAvailableCharacterGuid(realmId));
|
||||
}
|
||||
|
||||
uint32 ToCloud9Sidecar::GenerateItemGuid(uint16 realmId)
|
||||
{
|
||||
return uint32(TC9GetNextAvailableItemGuid(realmId));
|
||||
}
|
||||
|
||||
uint32 ToCloud9Sidecar::GenerateInstanceGuid(uint16 realmId)
|
||||
{
|
||||
return uint32(TC9GetNextAvailableInstanceGuid(realmId));
|
||||
}
|
||||
|
||||
void ToCloud9Sidecar::OnPlayerLeftBattleground(uint64 playerGUID, uint32 realmID, uint32 instanceID)
|
||||
{
|
||||
TC9PlayerLeftBattleground(playerGUID, realmID, instanceID);
|
||||
}
|
||||
|
||||
void ToCloud9Sidecar::OnBattlegroundStatusChanged(uint32 instanceID, uint8 status)
|
||||
{
|
||||
TC9BattlegroundStatusChanged(instanceID, status);
|
||||
}
|
||||
|
||||
bool ToCloud9Sidecar::NatsPublish(std::string const& subject, std::string const& payload)
|
||||
{
|
||||
if (!_clusterModeEnabled)
|
||||
return false;
|
||||
|
||||
if (payload.size() > size_t(std::numeric_limits<int>::max()))
|
||||
return false;
|
||||
|
||||
return TC9NatsPublish(subject.c_str(), payload.c_str(), int(payload.size())) == 0;
|
||||
}
|
||||
|
||||
bool ToCloud9Sidecar::NatsSubscribe(std::string const& subject, void (*handler)(char const*, char const*, int))
|
||||
{
|
||||
if (!_clusterModeEnabled || !handler)
|
||||
return false;
|
||||
|
||||
return TC9NatsSubscribe(subject.c_str(), handler) == 0;
|
||||
}
|
||||
|
||||
void ToCloud9Sidecar::OnMapsReassigned(uint32* addedMaps, int addedMapsSize, uint32* removedMaps, int removedMapsSize)
|
||||
{
|
||||
std::vector<uint32_t> newMapIDs;
|
||||
newMapIDs.reserve(addedMapsSize > 0 ? addedMapsSize : 0);
|
||||
|
||||
for (int i = 0; i < addedMapsSize; i++)
|
||||
{
|
||||
uint32 mapId = addedMaps[i];
|
||||
if (mapId >= MAX_MAP_ID)
|
||||
{
|
||||
LOG_ERROR("server", "ToCloud9Sidecar::OnMapsReassigned: added map id {} out of range [0, {}), ignored",
|
||||
mapId, MAX_MAP_ID);
|
||||
continue;
|
||||
}
|
||||
|
||||
sToCloud9Sidecar->_assignedMapsByID[mapId] = true;
|
||||
newMapIDs.push_back(mapId);
|
||||
|
||||
if (Map* map = sMapMgr->FindBaseNonInstanceMap(mapId))
|
||||
map->StopPlayersRedirectKickTimer();
|
||||
}
|
||||
|
||||
for (int i = 0; i < removedMapsSize; i++)
|
||||
{
|
||||
uint32 mapId = removedMaps[i];
|
||||
if (mapId >= MAX_MAP_ID)
|
||||
{
|
||||
LOG_ERROR("server", "ToCloud9Sidecar::OnMapsReassigned: removed map id {} out of range [0, {}), ignored",
|
||||
mapId, MAX_MAP_ID);
|
||||
continue;
|
||||
}
|
||||
|
||||
sToCloud9Sidecar->_assignedMapsByID[mapId] = false;
|
||||
|
||||
if (Map* map = sMapMgr->FindBaseNonInstanceMap(mapId))
|
||||
map->StartPlayersRedirectKickTimer();
|
||||
}
|
||||
|
||||
if (!newMapIDs.empty())
|
||||
{
|
||||
auto loadRowsPtr = std::make_shared<InstanceMapLoadRows>();
|
||||
|
||||
AsyncTask<bool> task(
|
||||
[loadRowsPtr, newMapIDs]() -> bool {
|
||||
LOG_INFO("server", "Starting to load data for newly assigned maps...");
|
||||
|
||||
*loadRowsPtr = sInstanceSaveMgr->LoadInstanceSavesAndBindsForMapIDs(newMapIDs);
|
||||
return true;
|
||||
},
|
||||
[loadRowsPtr, newMapIDs](bool) {
|
||||
sInstanceSaveMgr->MergeWithNewInstanceSaves(*loadRowsPtr);
|
||||
TC9ReadyToAcceptPlayersFromMaps((uint32_t*)newMapIDs.data(), newMapIDs.size());
|
||||
|
||||
LOG_INFO("server", "Finished loading data for newly assigned maps.");
|
||||
}
|
||||
);
|
||||
|
||||
task.ExecuteAsync();
|
||||
sToCloud9Sidecar->_asyncTasksProcessor.AddCallback(std::move(task));
|
||||
}
|
||||
}
|
||||
|
||||
MonitoringDataCollectorResponse HandleMonitoringRequest()
|
||||
{
|
||||
MonitoringDataCollectorResponse res;
|
||||
res.errorCode = MonitoringErrorCodeNoError;
|
||||
res.diffMean = sWorldUpdateTime.GetAverageUpdateTime();
|
||||
res.diffMedian = sWorldUpdateTime.GetPercentile(50);
|
||||
res.diff95Percentile = sWorldUpdateTime.GetPercentile(95);
|
||||
res.diff99Percentile = sWorldUpdateTime.GetPercentile(99);
|
||||
res.diffMaxPercentile = sWorldUpdateTime.GetPercentile(100);
|
||||
res.connectedPlayers = sWorldSessionMgr->GetActiveSessionCount();
|
||||
return res;
|
||||
}
|
||||
@@ -0,0 +1,78 @@
|
||||
/*
|
||||
* 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/>.
|
||||
*/
|
||||
|
||||
#ifndef _TC9_SIDECAR_H
|
||||
#define _TC9_SIDECAR_H
|
||||
|
||||
#include "AsyncCallbackProcessor.h"
|
||||
#include "AsyncTask.h"
|
||||
#include "Common.h"
|
||||
#include "ObjectGuid.h"
|
||||
|
||||
#define MAX_MAP_ID 800 // Probably too much, but let's lean towards caution.
|
||||
|
||||
class ToCloud9Sidecar
|
||||
{
|
||||
private:
|
||||
ToCloud9Sidecar();
|
||||
~ToCloud9Sidecar() {};
|
||||
|
||||
public:
|
||||
static ToCloud9Sidecar* instance();
|
||||
|
||||
void Init(uint16 port, int realmId);
|
||||
void Deinit();
|
||||
|
||||
bool ClusterModeEnabled() { return _clusterModeEnabled; }
|
||||
bool IsCrossrealm() { return _isCrossrealm; }
|
||||
|
||||
bool IsMapAssigned(uint32 mapId);
|
||||
|
||||
void SetupHooks();
|
||||
void SetupGrpcHandlers();
|
||||
|
||||
void ProcessHooks();
|
||||
void ProcessGrpcOrHttpRequests();
|
||||
void ProcessAsyncTasks();
|
||||
|
||||
uint32 GenerateCharacterGuid(uint16 realmId = DEFAULT_NON_CROSSREALM_REALM_ID);
|
||||
uint32 GenerateItemGuid(uint16 realmId = DEFAULT_NON_CROSSREALM_REALM_ID);
|
||||
uint32 GenerateInstanceGuid(uint16 realmId = DEFAULT_NON_CROSSREALM_REALM_ID);
|
||||
|
||||
void OnPlayerLeftBattleground(uint64 playerGUID, uint32 realmID, uint32 instanceID);
|
||||
void OnBattlegroundStatusChanged(uint32 instanceID, uint8 status);
|
||||
|
||||
// Generic NATS pub/sub (single choke point for in-process modules).
|
||||
// No-ops outside cluster mode. Subscribe callbacks run on the world
|
||||
// thread (ProcessHooks).
|
||||
bool NatsPublish(std::string const& subject, std::string const& payload);
|
||||
bool NatsSubscribe(std::string const& subject, void (*handler)(char const* subject, char const* payload, int payloadLen));
|
||||
|
||||
private:
|
||||
static void OnMapsReassigned(uint32* addedMaps, int addedMapsSize, uint32* removedMaps, int removedMapsSize);
|
||||
|
||||
bool _clusterModeEnabled;
|
||||
bool _isCrossrealm;
|
||||
|
||||
bool _assignedMapsByID[MAX_MAP_ID];
|
||||
|
||||
AsyncCallbackProcessor<AsyncTask<bool>> _asyncTasksProcessor;
|
||||
};
|
||||
|
||||
#define sToCloud9Sidecar ToCloud9Sidecar::instance()
|
||||
|
||||
#endif // _TC9_SIDECAR_H
|
||||
@@ -23,8 +23,8 @@
|
||||
#include "AccountMgr.h"
|
||||
#include "AchievementMgr.h"
|
||||
#include "AddonMgr.h"
|
||||
#include "ArenaTeamMgr.h"
|
||||
#include "ArenaSeasonMgr.h"
|
||||
#include "ArenaTeamMgr.h"
|
||||
#include "AuctionHouseMgr.h"
|
||||
#include "AutobroadcastMgr.h"
|
||||
#include "BattlefieldMgr.h"
|
||||
@@ -42,6 +42,7 @@
|
||||
#include "CreatureGroups.h"
|
||||
#include "CreatureTextMgr.h"
|
||||
#include "DBCStores.h"
|
||||
#include "DBUpdater.h"
|
||||
#include "DatabaseEnv.h"
|
||||
#include "DisableMgr.h"
|
||||
#include "DynamicVisibility.h"
|
||||
@@ -52,8 +53,8 @@
|
||||
#include "GridNotifiersImpl.h"
|
||||
#include "GroupMgr.h"
|
||||
#include "GuildMgr.h"
|
||||
#include "IPLocation.h"
|
||||
#include "InstanceSaveMgr.h"
|
||||
#include "IPLocation.h"
|
||||
#include "ItemEnchantmentMgr.h"
|
||||
#include "LFGMgr.h"
|
||||
#include "Language.h"
|
||||
@@ -82,6 +83,7 @@
|
||||
#include "SmartAI.h"
|
||||
#include "SpellMgr.h"
|
||||
#include "TaskScheduler.h"
|
||||
#include "TC9Sidecar.h"
|
||||
#include "TicketMgr.h"
|
||||
#include "Transport.h"
|
||||
#include "TransportMgr.h"
|
||||
@@ -1059,6 +1061,13 @@ void World::SetInitialWorldSettings()
|
||||
if (sConfigMgr->isDryRun())
|
||||
{
|
||||
sMapMgr->UnloadAll();
|
||||
|
||||
if (uint32 failed = DBUpdaterUtil::GetFailedUpdateCount())
|
||||
{
|
||||
LOG_FATAL("server.loading", "AzerothCore Dry Run Completed With {} Failed Database Update(s), Terminating.", failed);
|
||||
exit(1);
|
||||
}
|
||||
|
||||
LOG_INFO("server.loading", "AzerothCore Dry Run Completed, Terminating.");
|
||||
exit(0);
|
||||
}
|
||||
@@ -1337,6 +1346,24 @@ void World::Update(uint32 diff)
|
||||
sScriptMgr->OnWorldUpdate(diff);
|
||||
}
|
||||
|
||||
if (sToCloud9Sidecar->ClusterModeEnabled())
|
||||
{
|
||||
{
|
||||
METRIC_TIMER("world_update_time", METRIC_TAG("type", "Process TC9 async tasks"));
|
||||
sToCloud9Sidecar->ProcessAsyncTasks();
|
||||
}
|
||||
|
||||
{
|
||||
METRIC_TIMER("world_update_time", METRIC_TAG("type", "Process TC9 hooks"));
|
||||
sToCloud9Sidecar->ProcessHooks();
|
||||
}
|
||||
|
||||
{
|
||||
METRIC_TIMER("world_update_time", METRIC_TAG("type", "Process TC9 gRPC and HTTP requests"));
|
||||
sToCloud9Sidecar->ProcessGrpcOrHttpRequests();
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
METRIC_TIMER("world_update_time", METRIC_TAG("type", "Update metrics"));
|
||||
// Stats logger update
|
||||
|
||||
@@ -168,6 +168,7 @@ void WorldConfig::BuildConfigCache()
|
||||
SetConfigValue<uint32>(CONFIG_INTERVAL_SAVE, "PlayerSaveInterval", 900000);
|
||||
SetConfigValue<uint32>(CONFIG_INTERVAL_DISCONNECT_TOLERANCE, "DisconnectToleranceInterval", 0);
|
||||
SetConfigValue<bool>(CONFIG_STATS_SAVE_ONLY_ON_LOGOUT, "PlayerSave.Stats.SaveOnlyOnLogout", true);
|
||||
SetConfigValue<uint32>(CONFIG_ADDITIONAL_SAVES, "PlayerSave.AdditionalSaves", 0);
|
||||
SetConfigValue<bool>(CONFIG_VALIDATE_SKILL_LEARNED_BY_SPELLS, "ValidateSkillLearnedBySpells", true);
|
||||
|
||||
SetConfigValue<uint32>(CONFIG_MIN_LEVEL_STAT_SAVE, "PlayerSave.Stats.MinLevel", 0, ConfigValueCache::Reloadable::Yes, [](uint32 const& value) { return value < MAX_LEVEL; }, "< MAX_LEVEL");
|
||||
|
||||
@@ -27,6 +27,7 @@ enum ServerConfigs
|
||||
CONFIG_ALLOW_PLAYER_COMMANDS,
|
||||
CONFIG_CLEAN_CHARACTER_DB,
|
||||
CONFIG_STATS_SAVE_ONLY_ON_LOGOUT,
|
||||
CONFIG_ADDITIONAL_SAVES,
|
||||
CONFIG_ALLOW_TWO_SIDE_ACCOUNTS,
|
||||
CONFIG_ALLOW_TWO_SIDE_INTERACTION_CALENDAR,
|
||||
CONFIG_ALLOW_TWO_SIDE_INTERACTION_CHAT,
|
||||
|
||||
@@ -22,6 +22,7 @@
|
||||
#include "MapMgr.h"
|
||||
#include "Player.h"
|
||||
#include "SharedDefines.h"
|
||||
#include "TC9Sidecar.h"
|
||||
#include "UnitAI.h"
|
||||
#include "Weather.h"
|
||||
#include "WorldState.h"
|
||||
@@ -160,20 +161,27 @@ void WorldState::LoadWorldStates()
|
||||
// Setting a worldstate will save it to DB
|
||||
void WorldState::setWorldState(uint32 index, uint64 timeValue)
|
||||
{
|
||||
auto const& it = _worldstates.find(index);
|
||||
if (it != _worldstates.end())
|
||||
// Crossrealm nodes must not persist worldstates: their CharacterDatabase is
|
||||
// the routing proxy and the write would land in an arbitrary realm DB. The
|
||||
// in-memory value still has to be updated so read-modify-write users
|
||||
// (e.g. the Wintergrasp clock) keep working.
|
||||
if (!sToCloud9Sidecar->IsCrossrealm())
|
||||
{
|
||||
CharacterDatabasePreparedStatement* stmt = CharacterDatabase.GetPreparedStatement(CHAR_UPD_WORLDSTATE);
|
||||
stmt->SetData(0, uint32(timeValue));
|
||||
stmt->SetData(1, index);
|
||||
CharacterDatabase.Execute(stmt);
|
||||
}
|
||||
else
|
||||
{
|
||||
CharacterDatabasePreparedStatement* stmt = CharacterDatabase.GetPreparedStatement(CHAR_INS_WORLDSTATE);
|
||||
stmt->SetData(0, index);
|
||||
stmt->SetData(1, uint32(timeValue));
|
||||
CharacterDatabase.Execute(stmt);
|
||||
auto const& it = _worldstates.find(index);
|
||||
if (it != _worldstates.end())
|
||||
{
|
||||
CharacterDatabasePreparedStatement* stmt = CharacterDatabase.GetPreparedStatement(CHAR_UPD_WORLDSTATE);
|
||||
stmt->SetData(0, uint32(timeValue));
|
||||
stmt->SetData(1, index);
|
||||
CharacterDatabase.Execute(stmt);
|
||||
}
|
||||
else
|
||||
{
|
||||
CharacterDatabasePreparedStatement* stmt = CharacterDatabase.GetPreparedStatement(CHAR_INS_WORLDSTATE);
|
||||
stmt->SetData(0, index);
|
||||
stmt->SetData(1, uint32(timeValue));
|
||||
CharacterDatabase.Execute(stmt);
|
||||
}
|
||||
}
|
||||
|
||||
_worldstates[index] = timeValue;
|
||||
|
||||
@@ -232,7 +232,8 @@ target_link_libraries(scripts
|
||||
PRIVATE
|
||||
acore-core-interface
|
||||
PUBLIC
|
||||
game-interface)
|
||||
game-interface
|
||||
libsidecar)
|
||||
|
||||
target_include_directories(scripts
|
||||
PUBLIC
|
||||
|
||||
@@ -1,477 +0,0 @@
|
||||
/*
|
||||
* 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 General Public License as published by
|
||||
* the Free Software Foundation; either version 2 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 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 "CombatAI.h"
|
||||
#include "CreatureScript.h"
|
||||
#include "CreatureTextMgr.h"
|
||||
#include "GameObjectScript.h"
|
||||
#include "MoveSplineInit.h"
|
||||
#include "ObjectMgr.h"
|
||||
#include "PassiveAI.h"
|
||||
#include "Player.h"
|
||||
#include "ScriptedCreature.h"
|
||||
#include "ScriptedEscortAI.h"
|
||||
#include "ScriptedGossip.h"
|
||||
#include "SpellInfo.h"
|
||||
#include "SpellScript.h"
|
||||
#include "SpellScriptLoader.h"
|
||||
|
||||
/*######
|
||||
## npc_eye_of_acherus
|
||||
######*/
|
||||
|
||||
enum EyeOfAcherusMisc
|
||||
{
|
||||
SPELL_THE_EYE_OF_ACHERUS = 51852,
|
||||
};
|
||||
|
||||
enum DeathComesFromOnHigh
|
||||
{
|
||||
SPELL_CALL_OF_THE_DEAD = 51900
|
||||
};
|
||||
|
||||
enum GothikActions
|
||||
{
|
||||
ACTION_DK_INITIATE_ASSAULT_ROAR = 15
|
||||
};
|
||||
|
||||
// 51904 - Summon Ghouls On Scarlet Crusade
|
||||
class spell_q12641_death_comes_from_on_high_summon_ghouls : public SpellScript
|
||||
{
|
||||
PrepareSpellScript(spell_q12641_death_comes_from_on_high_summon_ghouls);
|
||||
|
||||
bool Validate(SpellInfo const* /*spellInfo*/) override
|
||||
{
|
||||
return ValidateSpellInfo({ SPELL_CALL_OF_THE_DEAD });
|
||||
}
|
||||
|
||||
void HandleScriptEffect(SpellEffIndex effIndex)
|
||||
{
|
||||
PreventHitEffect(effIndex);
|
||||
if (Unit* target = GetHitUnit())
|
||||
target->CastSpell(target, SPELL_CALL_OF_THE_DEAD, true);
|
||||
}
|
||||
|
||||
void Register() override
|
||||
{
|
||||
OnEffectHitTarget += SpellEffectFn(spell_q12641_death_comes_from_on_high_summon_ghouls::HandleScriptEffect, EFFECT_0, SPELL_EFFECT_SCRIPT_EFFECT);
|
||||
}
|
||||
};
|
||||
|
||||
// 52694 - Recall Eye of Acherus
|
||||
class spell_q12641_death_comes_from_on_high_recall_eye : public SpellScript
|
||||
{
|
||||
PrepareSpellScript(spell_q12641_death_comes_from_on_high_recall_eye);
|
||||
|
||||
bool Validate(SpellInfo const* /*spellInfo*/) override
|
||||
{
|
||||
return ValidateSpellInfo({ SPELL_THE_EYE_OF_ACHERUS });
|
||||
}
|
||||
|
||||
void HandleScriptEffect(SpellEffIndex effIndex)
|
||||
{
|
||||
PreventHitEffect(effIndex);
|
||||
Unit* caster = GetCaster();
|
||||
Unit* owner = caster->GetCharmerOrOwner();
|
||||
|
||||
if (!caster || !owner)
|
||||
return;
|
||||
|
||||
if (owner->HasAura(SPELL_THE_EYE_OF_ACHERUS))
|
||||
owner->RemoveAurasDueToSpell(SPELL_THE_EYE_OF_ACHERUS);
|
||||
}
|
||||
|
||||
void Register() override
|
||||
{
|
||||
OnEffectHitTarget += SpellEffectFn(spell_q12641_death_comes_from_on_high_recall_eye::HandleScriptEffect, EFFECT_0, SPELL_EFFECT_SCRIPT_EFFECT);
|
||||
}
|
||||
};
|
||||
|
||||
// 51761 - Rain of Darkness
|
||||
class spell_q12641_rain_of_darkness : public SpellScript
|
||||
{
|
||||
PrepareSpellScript(spell_q12641_rain_of_darkness);
|
||||
|
||||
void ModDestHeight(SpellDestination& dest)
|
||||
{
|
||||
Position const offset = { 0.0f, 0.0f, 15.0f, 0.0f };
|
||||
dest.RelocateOffset(offset);
|
||||
}
|
||||
|
||||
void Register() override
|
||||
{
|
||||
OnDestinationTargetSelect += SpellDestinationTargetSelectFn(spell_q12641_rain_of_darkness::ModDestHeight, EFFECT_0, TARGET_DEST_CASTER_BACK);
|
||||
}
|
||||
};
|
||||
|
||||
enum GiftOfTheHarvester
|
||||
{
|
||||
NPC_GHOUL = 28845,
|
||||
MAX_GHOULS = 5,
|
||||
|
||||
SPELL_GHOUL_EMERGE = 50142,
|
||||
SPELL_SUMMON_SCARLET_GHOST = 52505,
|
||||
SPELL_GHOUL_SUBMERGE = 26234,
|
||||
|
||||
EVENT_GHOUL_RESTORE_STATE = 1,
|
||||
EVENT_GHOUL_CHECK_COMBAT = 2,
|
||||
EVENT_GHOUL_EMOTE = 3,
|
||||
EVENT_GHOUL_MOVE_TO_PIT = 4,
|
||||
|
||||
SAY_GOTHIK_PIT = 0
|
||||
};
|
||||
|
||||
class spell_item_gift_of_the_harvester : public SpellScript
|
||||
{
|
||||
PrepareSpellScript(spell_item_gift_of_the_harvester);
|
||||
|
||||
SpellCastResult CheckRequirement()
|
||||
{
|
||||
std::list<Creature*> ghouls;
|
||||
GetCaster()->GetAllMinionsByEntry(ghouls, NPC_GHOUL);
|
||||
if (ghouls.size() >= MAX_GHOULS)
|
||||
{
|
||||
SetCustomCastResultMessage(SPELL_CUSTOM_ERROR_TOO_MANY_GHOULS);
|
||||
return SPELL_FAILED_CUSTOM_ERROR;
|
||||
}
|
||||
|
||||
return SPELL_CAST_OK;
|
||||
}
|
||||
|
||||
void Register() override
|
||||
{
|
||||
OnCheckCast += SpellCheckCastFn(spell_item_gift_of_the_harvester::CheckRequirement);
|
||||
}
|
||||
};
|
||||
|
||||
class spell_q12698_the_gift_that_keeps_on_giving : public SpellScript
|
||||
{
|
||||
PrepareSpellScript(spell_q12698_the_gift_that_keeps_on_giving);
|
||||
|
||||
bool Validate(SpellInfo const* /*spellInfo*/) override
|
||||
{
|
||||
return ValidateSpellInfo({ SPELL_SUMMON_SCARLET_GHOST });
|
||||
}
|
||||
|
||||
void HandleScriptEffect(SpellEffIndex /*effIndex*/)
|
||||
{
|
||||
if (GetOriginalCaster() && GetHitUnit())
|
||||
GetOriginalCaster()->CastSpell(GetHitUnit(), urand(0, 1) ? GetEffectValue() : SPELL_SUMMON_SCARLET_GHOST, true);
|
||||
}
|
||||
|
||||
void Register() override
|
||||
{
|
||||
OnEffectHitTarget += SpellEffectFn(spell_q12698_the_gift_that_keeps_on_giving::HandleScriptEffect, EFFECT_0, SPELL_EFFECT_SCRIPT_EFFECT);
|
||||
}
|
||||
};
|
||||
|
||||
class npc_scarlet_ghoul : public CreatureScript
|
||||
{
|
||||
public:
|
||||
npc_scarlet_ghoul() : CreatureScript("npc_scarlet_ghoul") { }
|
||||
|
||||
CreatureAI* GetAI(Creature* creature) const override
|
||||
{
|
||||
return new npc_scarlet_ghoulAI(creature);
|
||||
}
|
||||
|
||||
struct npc_scarlet_ghoulAI : public ScriptedAI
|
||||
{
|
||||
npc_scarlet_ghoulAI(Creature* creature) : ScriptedAI(creature)
|
||||
{
|
||||
}
|
||||
|
||||
EventMap events;
|
||||
ObjectGuid gothikGUID;
|
||||
|
||||
void InitializeAI() override
|
||||
{
|
||||
me->SetUnitFlag(UNIT_FLAG_DISABLE_MOVE);
|
||||
ScriptedAI::InitializeAI();
|
||||
me->SetReactState(REACT_PASSIVE);
|
||||
|
||||
events.ScheduleEvent(EVENT_GHOUL_EMOTE, 1ms);
|
||||
events.ScheduleEvent(EVENT_GHOUL_RESTORE_STATE, 3500ms);
|
||||
}
|
||||
|
||||
void OwnerAttackedBy(Unit* attacker) override
|
||||
{
|
||||
if (!me->IsInCombat() && me->GetReactState() == REACT_DEFENSIVE)
|
||||
AttackStart(attacker);
|
||||
}
|
||||
|
||||
void SetGUID(ObjectGuid const& guid, int32) override
|
||||
{
|
||||
gothikGUID = guid;
|
||||
events.ScheduleEvent(EVENT_GHOUL_MOVE_TO_PIT, 3s);
|
||||
me->GetMotionMaster()->Clear(false);
|
||||
}
|
||||
|
||||
void MovementInform(uint32 type, uint32 point) override
|
||||
{
|
||||
if (type == POINT_MOTION_TYPE && point == 1)
|
||||
{
|
||||
me->DespawnOrUnsummon(1500ms);
|
||||
me->CastSpell(me, SPELL_GHOUL_SUBMERGE, true);
|
||||
}
|
||||
}
|
||||
|
||||
void UpdateAI(uint32 diff) override
|
||||
{
|
||||
events.Update(diff);
|
||||
switch (events.ExecuteEvent())
|
||||
{
|
||||
case EVENT_GHOUL_MOVE_TO_PIT:
|
||||
me->GetMotionMaster()->MovePoint(1, 2364.77f, -5776.14f, 151.36f);
|
||||
if (Creature* gothik = ObjectAccessor::GetCreature(*me, gothikGUID))
|
||||
gothik->AI()->DoAction(SAY_GOTHIK_PIT);
|
||||
break;
|
||||
case EVENT_GHOUL_EMOTE:
|
||||
me->CastSpell(me, SPELL_GHOUL_EMERGE, true);
|
||||
break;
|
||||
case EVENT_GHOUL_RESTORE_STATE:
|
||||
me->SetReactState(REACT_DEFENSIVE);
|
||||
me->RemoveUnitFlag(UNIT_FLAG_DISABLE_MOVE);
|
||||
if (Player* owner = me->GetCharmerOrOwnerPlayerOrPlayerItself())
|
||||
me->GetMotionMaster()->MoveFollow(owner, PET_FOLLOW_DIST, frand(0.0f, 2 * M_PI));
|
||||
events.ScheduleEvent(EVENT_GHOUL_CHECK_COMBAT, 1s);
|
||||
return;
|
||||
case EVENT_GHOUL_CHECK_COMBAT:
|
||||
if (!me->IsInCombat())
|
||||
if (Player* owner = me->GetCharmerOrOwnerPlayerOrPlayerItself())
|
||||
if (owner->GetVictim())
|
||||
AttackStart(owner->GetVictim());
|
||||
|
||||
events.Repeat(1s);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!UpdateVictim())
|
||||
return;
|
||||
|
||||
DoMeleeAttackIfReady();
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
class npc_dkc1_gothik : public CreatureScript
|
||||
{
|
||||
public:
|
||||
npc_dkc1_gothik() : CreatureScript("npc_dkc1_gothik") { }
|
||||
|
||||
CreatureAI* GetAI(Creature* creature) const override
|
||||
{
|
||||
return new npc_dkc1_gothikAI(creature);
|
||||
}
|
||||
|
||||
struct npc_dkc1_gothikAI : public ScriptedAI
|
||||
{
|
||||
npc_dkc1_gothikAI(Creature* creature) : ScriptedAI(creature) { spoken = 0; }
|
||||
|
||||
int32 spoken;
|
||||
|
||||
void DoAction(int32 action) override
|
||||
{
|
||||
if (action == SAY_GOTHIK_PIT && spoken <= 0)
|
||||
{
|
||||
spoken = 5000;
|
||||
Talk(SAY_GOTHIK_PIT);
|
||||
}
|
||||
|
||||
if (action == ACTION_DK_INITIATE_ASSAULT_ROAR)
|
||||
me->HandleEmoteCommand(EMOTE_ONESHOT_ROAR);
|
||||
}
|
||||
|
||||
void MoveInLineOfSight(Unit* who) override
|
||||
{
|
||||
ScriptedAI::MoveInLineOfSight(who);
|
||||
|
||||
if (!who->IsImmuneToNPC() && who->GetEntry() == NPC_GHOUL && me->IsWithinDistInMap(who, 10.0f))
|
||||
if (Unit* owner = who->GetOwner())
|
||||
if (Player* player = owner->ToPlayer())
|
||||
{
|
||||
Creature* creature = who->ToCreature();
|
||||
if (player->GetQuestStatus(12698) == QUEST_STATUS_INCOMPLETE)
|
||||
creature->CastSpell(owner, 52517, true);
|
||||
|
||||
creature->AI()->SetGUID(me->GetGUID());
|
||||
creature->SetImmuneToAll(true);
|
||||
}
|
||||
}
|
||||
|
||||
void UpdateAI(uint32 diff) override
|
||||
{
|
||||
if (spoken > 0)
|
||||
spoken -= diff;
|
||||
|
||||
ScriptedAI::UpdateAI(diff);
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
/*######
|
||||
##Quest 12848
|
||||
######*/
|
||||
|
||||
#define GCD_CAST 1
|
||||
|
||||
enum UnworthyInitiate
|
||||
{
|
||||
SPELL_DK_INITIATE_VISUAL = 51519,
|
||||
};
|
||||
|
||||
class spell_death_knight_initiate_visual : public SpellScript
|
||||
{
|
||||
PrepareSpellScript(spell_death_knight_initiate_visual);
|
||||
|
||||
void HandleScriptEffect(SpellEffIndex /* effIndex */)
|
||||
{
|
||||
Creature* target = GetHitCreature();
|
||||
if (!target)
|
||||
return;
|
||||
|
||||
uint32 spellId;
|
||||
switch (target->GetDisplayId())
|
||||
{
|
||||
case 25369: spellId = 51552; break; // bloodelf female
|
||||
case 25373: spellId = 51551; break; // bloodelf male
|
||||
case 25363: spellId = 51542; break; // draenei female
|
||||
case 25357: spellId = 51541; break; // draenei male
|
||||
case 25361: spellId = 51537; break; // dwarf female
|
||||
case 25356: spellId = 51538; break; // dwarf male
|
||||
case 25372: spellId = 51550; break; // forsaken female
|
||||
case 25367: spellId = 51549; break; // forsaken male
|
||||
case 25362: spellId = 51540; break; // gnome female
|
||||
case 25359: spellId = 51539; break; // gnome male
|
||||
case 25355: spellId = 51534; break; // human female
|
||||
case 25354: spellId = 51520; break; // human male
|
||||
case 25360: spellId = 51536; break; // nightelf female
|
||||
case 25358: spellId = 51535; break; // nightelf male
|
||||
case 25368: spellId = 51544; break; // orc female
|
||||
case 25364: spellId = 51543; break; // orc male
|
||||
case 25371: spellId = 51548; break; // tauren female
|
||||
case 25366: spellId = 51547; break; // tauren male
|
||||
case 25370: spellId = 51545; break; // troll female
|
||||
case 25365: spellId = 51546; break; // troll male
|
||||
default: return;
|
||||
}
|
||||
|
||||
target->CastSpell(target, spellId, true);
|
||||
target->LoadEquipment();
|
||||
}
|
||||
|
||||
void Register() override
|
||||
{
|
||||
OnEffectHitTarget += SpellEffectFn(spell_death_knight_initiate_visual::HandleScriptEffect, EFFECT_0, SPELL_EFFECT_SCRIPT_EFFECT);
|
||||
}
|
||||
};
|
||||
|
||||
enum spells_lich_king_whisper
|
||||
{
|
||||
SPELL_LICH_KING_VO_BLOCKER = 58207,
|
||||
SPELL_LICHKINGDK001 = 58208,
|
||||
SPELL_LICHKINGDK002 = 58209,
|
||||
SPELL_LICHKINGDK003 = 58210,
|
||||
SPELL_LICHKINGDK004 = 58211,
|
||||
SPELL_LICHKINGDK005 = 58212,
|
||||
SPELL_LICHKINGDK006 = 58213,
|
||||
SPELL_LICHKINGDK007 = 58214,
|
||||
SPELL_LICHKINGDK008 = 58215,
|
||||
SPELL_LICHKINGDK009 = 58216,
|
||||
SPELL_LICHKINGDK010 = 58217,
|
||||
SPELL_LICHKINGDK011 = 58218,
|
||||
SPELL_LICHKINGDK012 = 58219,
|
||||
SPELL_LICHKINGDK013 = 58220,
|
||||
SPELL_LICHKINGDK014 = 58221,
|
||||
SPELL_LICHKINGDK015 = 58222,
|
||||
SPELL_LICHKINGDK016 = 58223
|
||||
};
|
||||
|
||||
//spell 58207 rand Whisper
|
||||
class spell_lich_king_vo_blocker : public AuraScript
|
||||
{
|
||||
PrepareAuraScript(spell_lich_king_vo_blocker);
|
||||
|
||||
bool Validate(SpellInfo const* /*spellInfo*/) override
|
||||
{
|
||||
return ValidateSpellInfo
|
||||
({
|
||||
SPELL_LICHKINGDK001, SPELL_LICHKINGDK002, SPELL_LICHKINGDK003, SPELL_LICHKINGDK004,
|
||||
SPELL_LICHKINGDK005, SPELL_LICHKINGDK006, SPELL_LICHKINGDK007, SPELL_LICHKINGDK008,
|
||||
SPELL_LICHKINGDK009, SPELL_LICHKINGDK010, SPELL_LICHKINGDK011, SPELL_LICHKINGDK012,
|
||||
SPELL_LICHKINGDK013, SPELL_LICHKINGDK014, SPELL_LICHKINGDK015, SPELL_LICHKINGDK016
|
||||
});
|
||||
}
|
||||
|
||||
void HandleEffectApply(AuraEffect const* /*aurEff*/, AuraEffectHandleModes /*mode*/)
|
||||
{
|
||||
if (Player* target = GetTarget()->ToPlayer())
|
||||
{
|
||||
//spell 58208-58223
|
||||
GetCaster()->CastSpell(target, urand(SPELL_LICHKINGDK001, SPELL_LICHKINGDK016), true);
|
||||
}
|
||||
}
|
||||
|
||||
void Register() override
|
||||
{
|
||||
OnEffectApply += AuraEffectApplyFn(spell_lich_king_vo_blocker::HandleEffectApply, EFFECT_0, SPELL_AURA_DUMMY, AURA_EFFECT_HANDLE_REAL);
|
||||
}
|
||||
};
|
||||
|
||||
// 58208 - 58224 - Creature - The Lich King (28765) Whisper
|
||||
class spell_lich_king_whisper : public SpellScript
|
||||
{
|
||||
PrepareSpellScript(spell_lich_king_whisper);
|
||||
|
||||
bool Validate(SpellInfo const* spellInfo) override
|
||||
{
|
||||
return sObjectMgr->GetBroadcastText(uint32(spellInfo->GetEffect(EFFECT_0).CalcValue())) &&
|
||||
sSoundEntriesStore.LookupEntry(uint32(spellInfo->GetEffect(EFFECT_1).CalcValue()));
|
||||
}
|
||||
|
||||
void HandleScript(SpellEffIndex /*effIndex*/)
|
||||
{
|
||||
if (Player* player = GetHitPlayer())
|
||||
GetCaster()->Whisper(uint32(GetEffectValue()), player, false);
|
||||
}
|
||||
|
||||
void HandleDummy(SpellEffIndex /*effIndex*/)
|
||||
{
|
||||
if (Player* player = GetHitPlayer())
|
||||
player->PlayDistanceSound(uint32(GetEffectValue()), player);
|
||||
}
|
||||
|
||||
void Register() override
|
||||
{
|
||||
OnEffectHitTarget += SpellEffectFn(spell_lich_king_whisper::HandleScript, EFFECT_0, SPELL_EFFECT_SCRIPT_EFFECT);
|
||||
OnEffectHitTarget += SpellEffectFn(spell_lich_king_whisper::HandleDummy, EFFECT_1, SPELL_EFFECT_DUMMY);
|
||||
}
|
||||
};
|
||||
|
||||
void AddSC_the_scarlet_enclave_c1()
|
||||
{
|
||||
RegisterSpellScript(spell_q12641_death_comes_from_on_high_summon_ghouls);
|
||||
RegisterSpellScript(spell_q12641_death_comes_from_on_high_recall_eye);
|
||||
RegisterSpellScript(spell_q12641_rain_of_darkness);
|
||||
RegisterSpellScript(spell_item_gift_of_the_harvester);
|
||||
RegisterSpellScript(spell_q12698_the_gift_that_keeps_on_giving);
|
||||
new npc_scarlet_ghoul();
|
||||
new npc_dkc1_gothik();
|
||||
RegisterSpellScript(spell_death_knight_initiate_visual);
|
||||
RegisterSpellScript(spell_lich_king_whisper);
|
||||
RegisterSpellScript(spell_lich_king_vo_blocker);
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,91 +0,0 @@
|
||||
/*
|
||||
* 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 General Public License as published by
|
||||
* the Free Software Foundation; either version 2 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 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 "CreatureScript.h"
|
||||
#include "ScriptedCreature.h"
|
||||
#include "SpellAuras.h"
|
||||
#include "SpellInfo.h"
|
||||
#include "SpellScript.h"
|
||||
#include "SpellScriptLoader.h"
|
||||
|
||||
enum DevourHumanoid
|
||||
{
|
||||
NPC_HEARTHGLEN_CRUSADER = 29102,
|
||||
NPC_TIRISFAL_CRUSADER = 29103
|
||||
};
|
||||
|
||||
// 53110 - Devour Humanoid
|
||||
class spell_q12779_an_end_to_all_things : public SpellScript
|
||||
{
|
||||
PrepareSpellScript(spell_q12779_an_end_to_all_things);
|
||||
|
||||
SpellCastResult CheckCast()
|
||||
{
|
||||
if (Unit* caster = GetCaster())
|
||||
if (caster->FindNearestCreature(NPC_HEARTHGLEN_CRUSADER, 15.0f, true) || caster->FindNearestCreature(NPC_TIRISFAL_CRUSADER, 15.0f, true))
|
||||
return SPELL_CAST_OK;
|
||||
|
||||
return SPELL_FAILED_BAD_TARGETS;
|
||||
}
|
||||
|
||||
void HandleScriptEffect(SpellEffIndex /*effIndex*/)
|
||||
{
|
||||
if (Creature* c = GetHitUnit()->ToCreature())
|
||||
if (Unit* caster = GetCaster())
|
||||
{
|
||||
c->AI()->AttackStart(caster);
|
||||
c->CastSpell(caster, GetEffectValue(), true); // 53111
|
||||
}
|
||||
}
|
||||
|
||||
void Register() override
|
||||
{
|
||||
OnCheckCast += SpellCheckCastFn(spell_q12779_an_end_to_all_things::CheckCast);
|
||||
OnEffectHitTarget += SpellEffectFn(spell_q12779_an_end_to_all_things::HandleScriptEffect, EFFECT_0, SPELL_EFFECT_SCRIPT_EFFECT);
|
||||
}
|
||||
};
|
||||
|
||||
// 53111 - Devour Humanoid (casted by the devoured creature)
|
||||
class spell_q12779_an_end_to_all_things_devour_aura : public AuraScript
|
||||
{
|
||||
PrepareAuraScript(spell_q12779_an_end_to_all_things_devour_aura);
|
||||
|
||||
void OnRemove(AuraEffect const* /*aurEff*/, AuraEffectHandleModes /*mode*/)
|
||||
{
|
||||
Unit* caster = GetCaster();
|
||||
Unit* target = GetTarget();
|
||||
if (!caster || !target)
|
||||
return;
|
||||
|
||||
if (GetTargetApplication()->GetRemoveMode() == AURA_REMOVE_BY_EXPIRE)
|
||||
{
|
||||
caster->SetDisableGravity(true);
|
||||
Unit::Kill(target, caster);
|
||||
}
|
||||
}
|
||||
|
||||
void Register() override
|
||||
{
|
||||
AfterEffectRemove += AuraEffectRemoveFn(spell_q12779_an_end_to_all_things_devour_aura::OnRemove, EFFECT_0, SPELL_AURA_CONTROL_VEHICLE, AURA_EFFECT_HANDLE_REAL);
|
||||
}
|
||||
};
|
||||
|
||||
void AddSC_the_scarlet_enclave_c3()
|
||||
{
|
||||
RegisterSpellScript(spell_q12779_an_end_to_all_things);
|
||||
RegisterSpellScript(spell_q12779_an_end_to_all_things_devour_aura);
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,143 +0,0 @@
|
||||
/*
|
||||
* 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 General Public License as published by
|
||||
* the Free Software Foundation; either version 2 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 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 "CreatureScript.h"
|
||||
#include "PassiveAI.h"
|
||||
#include "Player.h"
|
||||
#include "ScriptedCreature.h"
|
||||
|
||||
/*####
|
||||
## npc_valkyr_battle_maiden
|
||||
####*/
|
||||
|
||||
enum Spells_VBM
|
||||
{
|
||||
SPELL_REVIVE = 51918
|
||||
};
|
||||
|
||||
enum Says_VBM
|
||||
{
|
||||
WHISPER_REVIVE = 0
|
||||
};
|
||||
|
||||
class npc_valkyr_battle_maiden : public CreatureScript
|
||||
{
|
||||
public:
|
||||
npc_valkyr_battle_maiden() : CreatureScript("npc_valkyr_battle_maiden") { }
|
||||
|
||||
CreatureAI* GetAI(Creature* creature) const override
|
||||
{
|
||||
return new npc_valkyr_battle_maidenAI(creature);
|
||||
}
|
||||
|
||||
struct npc_valkyr_battle_maidenAI : public PassiveAI
|
||||
{
|
||||
npc_valkyr_battle_maidenAI(Creature* creature) : PassiveAI(creature) { }
|
||||
|
||||
uint32 FlyBackTimer;
|
||||
float x, y, z;
|
||||
uint32 phase;
|
||||
|
||||
void Reset() override
|
||||
{
|
||||
me->setActive(true);
|
||||
me->SetVisible(false);
|
||||
me->SetUnitFlag(UNIT_FLAG_NON_ATTACKABLE);
|
||||
me->SetCanFly(true);
|
||||
FlyBackTimer = 500;
|
||||
phase = 0;
|
||||
|
||||
me->GetPosition(x, y, z);
|
||||
z += 4.0f;
|
||||
x -= 3.5f;
|
||||
y -= 5.0f;
|
||||
me->GetMotionMaster()->Clear(false);
|
||||
me->SetPosition(x, y, z, 0.0f);
|
||||
}
|
||||
|
||||
void UpdateAI(uint32 diff) override
|
||||
{
|
||||
if (FlyBackTimer <= diff)
|
||||
{
|
||||
Player* player = nullptr;
|
||||
if (me->IsSummon())
|
||||
{
|
||||
if (Unit * summoner = me->ToTempSummon()->GetSummonerUnit())
|
||||
{
|
||||
player = summoner->ToPlayer();
|
||||
}
|
||||
}
|
||||
|
||||
if (!player)
|
||||
{
|
||||
phase = 3;
|
||||
}
|
||||
|
||||
switch (phase)
|
||||
{
|
||||
case 0:
|
||||
me->SetWalk(false);
|
||||
me->HandleEmoteCommand(EMOTE_STATE_FLYGRABCLOSED);
|
||||
FlyBackTimer = 500;
|
||||
break;
|
||||
case 1:
|
||||
if (player)
|
||||
{
|
||||
player->GetClosePoint(x, y, z, me->GetObjectSize());
|
||||
}
|
||||
z += 2.5f;
|
||||
x -= 2.0f;
|
||||
y -= 1.5f;
|
||||
me->GetMotionMaster()->MovePoint(0, x, y, z);
|
||||
if (player)
|
||||
{
|
||||
me->SetTarget(player->GetGUID());
|
||||
}
|
||||
me->SetVisible(true);
|
||||
FlyBackTimer = 4500;
|
||||
break;
|
||||
case 2:
|
||||
if (player && !player->isResurrectRequested())
|
||||
{
|
||||
me->HandleEmoteCommand(EMOTE_ONESHOT_CUSTOM_SPELL_01);
|
||||
DoCast(player, SPELL_REVIVE, true);
|
||||
Talk(WHISPER_REVIVE, player);
|
||||
}
|
||||
FlyBackTimer = 5000;
|
||||
break;
|
||||
case 3:
|
||||
me->SetVisible(false);
|
||||
FlyBackTimer = 3000;
|
||||
break;
|
||||
case 4:
|
||||
me->DisappearAndDie();
|
||||
break;
|
||||
default:
|
||||
//Nothing To DO
|
||||
break;
|
||||
}
|
||||
++phase;
|
||||
}
|
||||
else FlyBackTimer -= diff;
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
void AddSC_the_scarlet_enclave()
|
||||
{
|
||||
new npc_valkyr_battle_maiden();
|
||||
}
|
||||
+59
-12
@@ -18,6 +18,8 @@
|
||||
#include "InstanceMapScript.h"
|
||||
#include "scarletmonastery.h"
|
||||
#include "ScriptedCreature.h"
|
||||
#include "SpellScript.h"
|
||||
#include "SpellScriptLoader.h"
|
||||
|
||||
enum AshbringerEventMisc
|
||||
{
|
||||
@@ -39,17 +41,6 @@ enum AshbringerEventMisc
|
||||
GO_HIGH_INQUISITOR_DOOR = 104600
|
||||
};
|
||||
|
||||
enum AshbringerSpell
|
||||
{
|
||||
//Highlord Mograine Spells
|
||||
//Needs Fix: Increased the visual effect of spells on hit
|
||||
SPELL_FORGIVENESS = 28697,
|
||||
|
||||
//High Inquisitor Fairbanks
|
||||
//Needs Fix: Increased the visual effect of spells on hit
|
||||
SPELL_TRANSFORM_GHOST = 28443
|
||||
};
|
||||
|
||||
enum DataTypes
|
||||
{
|
||||
TYPE_MOGRAINE_AND_WHITE_EVENT = 1,
|
||||
@@ -66,7 +57,7 @@ enum DataTypes
|
||||
GAMEOBJECT_PUMPKIN_SHRINE = 10
|
||||
};
|
||||
|
||||
float const CATHEDRAL_PULL_RANGE = 80.0f; // Distance from the Cathedral doors to where Mograine is standing
|
||||
float constexpr CATHEDRAL_PULL_RANGE = 80.0f; // Distance from the Cathedral doors to where Mograine is standing
|
||||
|
||||
class instance_scarlet_monastery : public InstanceMapScript
|
||||
{
|
||||
@@ -271,7 +262,63 @@ public:
|
||||
};
|
||||
};
|
||||
|
||||
enum AshbringerSpell
|
||||
{
|
||||
SPELL_FORGIVENESS = 28697,
|
||||
SPELL_FORGIVENESS_IMPACTKIT = 317,
|
||||
SPELL_TRANSFORM_GHOST = 28443,
|
||||
SPELL_TRANSFORM_IMPACTKIT=500
|
||||
};
|
||||
|
||||
// SPELL_FORGIVENESS = 28697
|
||||
class spell_forgiveness_dummy_visual : public SpellScript
|
||||
{
|
||||
PrepareSpellScript(spell_forgiveness_dummy_visual);
|
||||
|
||||
void HandleDummy(SpellEffIndex /*effIndex*/)
|
||||
{
|
||||
Unit* target = GetHitUnit();
|
||||
if (!target)
|
||||
return;
|
||||
|
||||
target->SendPlaySpellVisual(SPELL_FORGIVENESS_IMPACTKIT);//SPELL_FORGIVENESS IMPACTKIT 317 SpellVisualEntry.ImpactKit can't be used
|
||||
|
||||
//Delay death to prevent the death of the creature from interrupting the animation display
|
||||
target->m_Events.AddEventAtOffset([target]() -> void
|
||||
{
|
||||
target->KillSelf();
|
||||
}, 500ms);
|
||||
}
|
||||
|
||||
void Register() override
|
||||
{
|
||||
OnEffectHitTarget += SpellEffectFn(spell_forgiveness_dummy_visual::HandleDummy, EFFECT_0, SPELL_EFFECT_DUMMY);
|
||||
}
|
||||
};
|
||||
|
||||
// SPELL_TRANSFORM_GHOST = 28443
|
||||
class spell_transform_ghost_visual: public SpellScript
|
||||
{
|
||||
PrepareSpellScript(spell_transform_ghost_visual);
|
||||
|
||||
void HandleAfterHit()
|
||||
{
|
||||
Unit* target = GetHitUnit();
|
||||
if (!target)
|
||||
return;
|
||||
|
||||
target->SendPlaySpellVisual(SPELL_TRANSFORM_IMPACTKIT); //SPELL_TRANSFORM_GHOST IMPACTKIT 500
|
||||
}
|
||||
|
||||
void Register() override
|
||||
{
|
||||
AfterHit += SpellHitFn(spell_transform_ghost_visual::HandleAfterHit);
|
||||
}
|
||||
};
|
||||
|
||||
void AddSC_instance_scarlet_monastery()
|
||||
{
|
||||
new instance_scarlet_monastery();
|
||||
RegisterSpellScript(spell_forgiveness_dummy_visual);
|
||||
RegisterSpellScript(spell_transform_ghost_visual);
|
||||
}
|
||||
|
||||
@@ -97,10 +97,6 @@ void AddSC_boss_majordomo();
|
||||
void AddSC_boss_ragnaros();
|
||||
void AddSC_instance_molten_core();
|
||||
void AddSC_the_scarlet_enclave(); //Scarlet Enclave
|
||||
void AddSC_the_scarlet_enclave_c1();
|
||||
void AddSC_the_scarlet_enclave_c2();
|
||||
void AddSC_the_scarlet_enclave_c3();
|
||||
void AddSC_the_scarlet_enclave_c5();
|
||||
void AddSC_instance_scarlet_monastery(); //Scarlet Monastery
|
||||
void AddSC_boss_kirtonos_the_herald();
|
||||
void AddSC_boss_darkmaster_gandling();
|
||||
@@ -249,10 +245,6 @@ void AddEasternKingdomsScripts()
|
||||
AddSC_boss_ragnaros();
|
||||
AddSC_instance_molten_core();
|
||||
AddSC_the_scarlet_enclave(); //Scarlet Enclave
|
||||
AddSC_the_scarlet_enclave_c1();
|
||||
AddSC_the_scarlet_enclave_c2();
|
||||
AddSC_the_scarlet_enclave_c3();
|
||||
AddSC_the_scarlet_enclave_c5();
|
||||
AddSC_instance_scarlet_monastery(); //Scarlet Monastery
|
||||
AddSC_boss_kirtonos_the_herald();
|
||||
AddSC_boss_darkmaster_gandling();
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -84,14 +84,23 @@ enum Phases
|
||||
PHASE_LANDED // Phase 3 - Landed after Airphase - 40% health
|
||||
};
|
||||
|
||||
struct sOnyxMove
|
||||
// Ids 0-8 are reserved for the OnyxiaMoveData waypoints
|
||||
enum Points
|
||||
{
|
||||
uint8 CurrId, DestId;
|
||||
uint32 spellId;
|
||||
float x, y, z, o;
|
||||
POINT_GROUND_SOUTH = 10,
|
||||
POINT_TAKEOFF = 11,
|
||||
POINT_PRE_LAND = 12,
|
||||
POINT_LAND = 13
|
||||
};
|
||||
|
||||
static sOnyxMove OnyxiaMoveData[] =
|
||||
struct OnyxiaMove
|
||||
{
|
||||
uint8 CurrId, DestId;
|
||||
uint32 SpellId;
|
||||
float X, Y, Z, O;
|
||||
};
|
||||
|
||||
static OnyxiaMove const OnyxiaMoveData[] =
|
||||
{
|
||||
{0, 0, 0, -64.496f, -214.906f, -84.4f, 0.0f}, // south ground
|
||||
{1, 5, SPELL_BREATH_S_TO_N, -64.496f, -214.906f, -60.0f, 0.0f}, // south
|
||||
@@ -117,24 +126,25 @@ enum Yells
|
||||
struct boss_onyxia : public BossAI
|
||||
{
|
||||
public:
|
||||
boss_onyxia(Creature* pCreature) : BossAI(pCreature, DATA_ONYXIA)
|
||||
boss_onyxia(Creature* creature) : BossAI(creature, DATA_ONYXIA)
|
||||
{
|
||||
Initialize();
|
||||
}
|
||||
|
||||
void Initialize()
|
||||
{
|
||||
CurrentWP = 0;
|
||||
whelpSpam = false;
|
||||
whelpCount = 0;
|
||||
whelpSpamTimer = 0;
|
||||
bManyWhelpsAvailable = false;
|
||||
_phase = PHASE_NONE;
|
||||
_currentWP = 0;
|
||||
_whelpSpam = false;
|
||||
_whelpCount = 0;
|
||||
_whelpSpamTimer = 0;
|
||||
_manyWhelpsAvailable = false;
|
||||
}
|
||||
|
||||
void SetPhase(uint8 ph)
|
||||
{
|
||||
events.Reset();
|
||||
Phase = ph;
|
||||
_phase = ph;
|
||||
switch (ph)
|
||||
{
|
||||
case PHASE_GROUNDED:
|
||||
@@ -149,6 +159,8 @@ public:
|
||||
case PHASE_LANDED:
|
||||
events.ScheduleEvent(EVENT_START_PHASE_3, 5s);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -168,12 +180,14 @@ public:
|
||||
{
|
||||
switch (param)
|
||||
{
|
||||
case -1:
|
||||
if (bManyWhelpsAvailable)
|
||||
case ACTION_WHELP_SUMMONED:
|
||||
if (_manyWhelpsAvailable)
|
||||
{
|
||||
instance->SetData(DATA_WHELP_SUMMONED, 1);
|
||||
}
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -191,11 +205,11 @@ public:
|
||||
|
||||
void DamageTaken(Unit*, uint32& damage, DamageEffectType, SpellSchoolMask) override
|
||||
{
|
||||
if (me->HealthBelowPctDamaged(65, damage) && Phase == PHASE_GROUNDED)
|
||||
if (me->HealthBelowPctDamaged(65, damage) && _phase == PHASE_GROUNDED)
|
||||
{
|
||||
SetPhase(PHASE_AIRPHASE);
|
||||
}
|
||||
else if (me->HealthBelowPctDamaged(40, damage) && Phase == PHASE_AIRPHASE)
|
||||
else if (me->HealthBelowPctDamaged(40, damage) && _phase == PHASE_AIRPHASE)
|
||||
{
|
||||
me->InterruptNonMeleeSpells(false);
|
||||
SetPhase(PHASE_LANDED);
|
||||
@@ -211,7 +225,7 @@ public:
|
||||
return;
|
||||
}
|
||||
|
||||
if (summon->GetEntry() == NPC_ONYXIAN_LAIR_GUARD && Phase < PHASE_AIRPHASE)
|
||||
if (summon->GetEntry() == NPC_ONYXIAN_LAIR_GUARD && _phase < PHASE_AIRPHASE)
|
||||
{
|
||||
return;
|
||||
}
|
||||
@@ -232,11 +246,11 @@ public:
|
||||
|
||||
if (id < 9)
|
||||
{
|
||||
if (id > 0 && Phase == PHASE_AIRPHASE)
|
||||
if (id > 0 && _phase == PHASE_AIRPHASE)
|
||||
{
|
||||
me->SetFacingTo(OnyxiaMoveData[id].o);
|
||||
me->SetFacingTo(OnyxiaMoveData[id].O);
|
||||
me->SetSpeed(MOVE_RUN, 1.6f, false);
|
||||
CurrentWP = id;
|
||||
_currentWP = id;
|
||||
events.ScheduleEvent(EVENT_SPELL_FIREBALL_FIRST, 1s);
|
||||
}
|
||||
}
|
||||
@@ -244,50 +258,58 @@ public:
|
||||
{
|
||||
switch (id)
|
||||
{
|
||||
case 10:
|
||||
me->SetFacingTo(OnyxiaMoveData[0].o);
|
||||
case POINT_GROUND_SOUTH:
|
||||
me->SetFacingTo(OnyxiaMoveData[0].O);
|
||||
events.ScheduleEvent(EVENT_LIFTOFF, 0ms);
|
||||
break;
|
||||
case 11:
|
||||
me->SetFacingTo(OnyxiaMoveData[1].o);
|
||||
case POINT_TAKEOFF:
|
||||
me->SetFacingTo(OnyxiaMoveData[1].O);
|
||||
events.ScheduleEvent(EVENT_FLY_S_TO_N, 0ms);
|
||||
break;
|
||||
case 12:
|
||||
me->SetFacingTo(OnyxiaMoveData[1].o);
|
||||
case POINT_PRE_LAND:
|
||||
me->SetFacingTo(OnyxiaMoveData[1].O);
|
||||
events.ScheduleEvent(EVENT_LAND, 0ms);
|
||||
break;
|
||||
case 13:
|
||||
case POINT_LAND:
|
||||
me->SetCanFly(false);
|
||||
me->SetDisableGravity(false);
|
||||
me->SetSpeed(MOVE_RUN, me->GetCreatureTemplate()->speed_run, false);
|
||||
events.ScheduleEvent(EVENT_PHASE_3_ATTACK, 0ms);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void HandleWhelpSpam(const uint32 diff)
|
||||
// Summons one whelp at each of the two side caves
|
||||
void SummonWhelps()
|
||||
{
|
||||
if (whelpSpam)
|
||||
float angle = rand_norm() * 2 * M_PI;
|
||||
float dist = rand_norm() * 4.0f;
|
||||
me->CastSpell(-33.18f + std::cos(angle) * dist, -258.80f + std::sin(angle) * dist, -89.0f, SPELL_SUMMON_WHELP, true);
|
||||
me->CastSpell(-32.535f + std::cos(angle) * dist, -170.190f + std::sin(angle) * dist, -89.0f, SPELL_SUMMON_WHELP, true);
|
||||
}
|
||||
|
||||
void HandleWhelpSpam(uint32 diff)
|
||||
{
|
||||
if (_whelpSpam)
|
||||
{
|
||||
if (whelpCount < 40)
|
||||
if (_whelpCount < 40)
|
||||
{
|
||||
whelpSpamTimer -= diff;
|
||||
if (whelpSpamTimer <= 0)
|
||||
_whelpSpamTimer -= diff;
|
||||
if (_whelpSpamTimer <= 0)
|
||||
{
|
||||
float angle = rand_norm() * 2 * M_PI;
|
||||
float dist = rand_norm() * 4.0f;
|
||||
me->CastSpell(-33.18f + cos(angle) * dist, -258.80f + std::sin(angle) * dist, -89.0f, 17646, true);
|
||||
me->CastSpell(-32.535f + cos(angle) * dist, -170.190f + std::sin(angle) * dist, -89.0f, 17646, true);
|
||||
whelpCount += 2;
|
||||
whelpSpamTimer += 600;
|
||||
SummonWhelps();
|
||||
_whelpCount += 2;
|
||||
_whelpSpamTimer += 600;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
whelpSpam = false;
|
||||
whelpCount = 0;
|
||||
whelpSpamTimer = 0;
|
||||
_whelpSpam = false;
|
||||
_whelpCount = 0;
|
||||
_whelpSpamTimer = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -319,8 +341,6 @@ public:
|
||||
return;
|
||||
}
|
||||
|
||||
DoMeleeAttackIfReady();
|
||||
|
||||
switch (events.ExecuteEvent())
|
||||
{
|
||||
case EVENT_SPELL_WINGBUFFET:
|
||||
@@ -353,7 +373,7 @@ public:
|
||||
me->SetReactState(REACT_PASSIVE);
|
||||
me->StopMoving();
|
||||
DoResetThreatList();
|
||||
me->GetMotionMaster()->MovePoint(10, OnyxiaMoveData[0].x, OnyxiaMoveData[0].y, OnyxiaMoveData[0].z);
|
||||
me->GetMotionMaster()->MovePoint(POINT_GROUND_SOUTH, OnyxiaMoveData[0].X, OnyxiaMoveData[0].Y, OnyxiaMoveData[0].Z);
|
||||
break;
|
||||
}
|
||||
case EVENT_LIFTOFF:
|
||||
@@ -364,23 +384,23 @@ public:
|
||||
me->DisableSpline();
|
||||
me->SetCanFly(true);
|
||||
me->SetDisableGravity(true);
|
||||
me->SetOrientation(OnyxiaMoveData[0].o);
|
||||
me->SetOrientation(OnyxiaMoveData[0].O);
|
||||
me->SendMovementFlagUpdate();
|
||||
me->GetMotionMaster()->MoveTakeoff(11, OnyxiaMoveData[1].x + 1.0f, OnyxiaMoveData[1].y, OnyxiaMoveData[1].z, 12.0f);
|
||||
bManyWhelpsAvailable = true;
|
||||
me->GetMotionMaster()->MoveTakeoff(POINT_TAKEOFF, OnyxiaMoveData[1].X + 1.0f, OnyxiaMoveData[1].Y, OnyxiaMoveData[1].Z, 12.0f);
|
||||
_manyWhelpsAvailable = true;
|
||||
|
||||
events.RescheduleEvent(EVENT_END_MANY_WHELPS_TIME, 10s);
|
||||
break;
|
||||
}
|
||||
case EVENT_END_MANY_WHELPS_TIME:
|
||||
bManyWhelpsAvailable = false;
|
||||
_manyWhelpsAvailable = false;
|
||||
break;
|
||||
case EVENT_FLY_S_TO_N:
|
||||
{
|
||||
me->SetSpeed(MOVE_RUN, 2.95f, false);
|
||||
me->GetMotionMaster()->MovePoint(5, OnyxiaMoveData[5].x, OnyxiaMoveData[5].y, OnyxiaMoveData[5].z);
|
||||
me->GetMotionMaster()->MovePoint(5, OnyxiaMoveData[5].X, OnyxiaMoveData[5].Y, OnyxiaMoveData[5].Z);
|
||||
|
||||
whelpSpam = true;
|
||||
_whelpSpam = true;
|
||||
events.ScheduleEvent(EVENT_WHELP_SPAM, 90s);
|
||||
events.ScheduleEvent(EVENT_SUMMON_LAIR_GUARD, 30s);
|
||||
break;
|
||||
@@ -393,7 +413,7 @@ public:
|
||||
}
|
||||
case EVENT_WHELP_SPAM:
|
||||
{
|
||||
whelpSpam = true;
|
||||
_whelpSpam = true;
|
||||
events.Repeat(90s);
|
||||
break;
|
||||
}
|
||||
@@ -401,16 +421,16 @@ public:
|
||||
{
|
||||
Talk(SAY_PHASE_3_TRANS);
|
||||
me->SendMeleeAttackStop(me->GetVictim());
|
||||
me->GetMotionMaster()->MoveLand(13, OnyxiaMoveData[0].x + 1.0f, OnyxiaMoveData[0].y, OnyxiaMoveData[0].z, 12.0f);
|
||||
me->GetMotionMaster()->MoveLand(POINT_LAND, OnyxiaMoveData[0].X + 1.0f, OnyxiaMoveData[0].Y, OnyxiaMoveData[0].Z, 12.0f);
|
||||
DoResetThreatList();
|
||||
break;
|
||||
}
|
||||
case EVENT_SPELL_FIREBALL_FIRST:
|
||||
{
|
||||
if (Unit* v = SelectTarget(SelectTargetMethod::Random, 0, 200.0f, true))
|
||||
if (Unit* target = SelectTarget(SelectTargetMethod::Random, 0, 200.0f, true))
|
||||
{
|
||||
me->SetFacingToObject(v);
|
||||
DoCast(v, SPELL_FIREBALL);
|
||||
me->SetFacingToObject(target);
|
||||
DoCast(target, SPELL_FIREBALL);
|
||||
}
|
||||
|
||||
events.ScheduleEvent(EVENT_SPELL_FIREBALL_SECOND, 4s);
|
||||
@@ -418,66 +438,65 @@ public:
|
||||
}
|
||||
case EVENT_SPELL_FIREBALL_SECOND:
|
||||
{
|
||||
if (Unit* v = SelectTarget(SelectTargetMethod::Random, 0, 200.0f, true))
|
||||
if (Unit* target = SelectTarget(SelectTargetMethod::Random, 0, 200.0f, true))
|
||||
{
|
||||
me->SetFacingToObject(v);
|
||||
DoCast(v, SPELL_FIREBALL);
|
||||
me->SetFacingToObject(target);
|
||||
DoCast(target, SPELL_FIREBALL);
|
||||
}
|
||||
|
||||
uint8 rand = urand(0, 99);
|
||||
if (rand < 33)
|
||||
switch (urand(0, 2))
|
||||
{
|
||||
events.ScheduleEvent(EVENT_PHASE_2_STEP_CW, 4s);
|
||||
}
|
||||
else if (rand < 66)
|
||||
{
|
||||
events.ScheduleEvent(EVENT_PHASE_2_STEP_ACW, 4s);
|
||||
}
|
||||
else
|
||||
{
|
||||
events.ScheduleEvent(EVENT_PHASE_2_STEP_ACROSS, 4s);
|
||||
case 0:
|
||||
events.ScheduleEvent(EVENT_PHASE_2_STEP_CW, 4s);
|
||||
break;
|
||||
case 1:
|
||||
events.ScheduleEvent(EVENT_PHASE_2_STEP_ACW, 4s);
|
||||
break;
|
||||
default:
|
||||
events.ScheduleEvent(EVENT_PHASE_2_STEP_ACROSS, 4s);
|
||||
break;
|
||||
}
|
||||
break;
|
||||
}
|
||||
case EVENT_PHASE_2_STEP_CW:
|
||||
{
|
||||
uint8 newWP = CurrentWP + 1;
|
||||
uint8 newWP = _currentWP + 1;
|
||||
if (newWP > 8)
|
||||
{
|
||||
newWP = 1;
|
||||
}
|
||||
me->GetMotionMaster()->MovePoint(newWP, OnyxiaMoveData[newWP].x, OnyxiaMoveData[newWP].y, OnyxiaMoveData[newWP].z);
|
||||
me->GetMotionMaster()->MovePoint(newWP, OnyxiaMoveData[newWP].X, OnyxiaMoveData[newWP].Y, OnyxiaMoveData[newWP].Z);
|
||||
break;
|
||||
}
|
||||
case EVENT_PHASE_2_STEP_ACW:
|
||||
{
|
||||
uint8 newWP = CurrentWP - 1;
|
||||
uint8 newWP = _currentWP - 1;
|
||||
if (newWP < 1)
|
||||
{
|
||||
newWP = 8;
|
||||
}
|
||||
me->GetMotionMaster()->MovePoint(newWP, OnyxiaMoveData[newWP].x, OnyxiaMoveData[newWP].y, OnyxiaMoveData[newWP].z);
|
||||
me->GetMotionMaster()->MovePoint(newWP, OnyxiaMoveData[newWP].X, OnyxiaMoveData[newWP].Y, OnyxiaMoveData[newWP].Z);
|
||||
break;
|
||||
}
|
||||
case EVENT_PHASE_2_STEP_ACROSS:
|
||||
{
|
||||
Talk(EMOTE_BREATH);
|
||||
me->SetFacingTo(OnyxiaMoveData[CurrentWP].o);
|
||||
DoCastAOE(OnyxiaMoveData[CurrentWP].spellId);
|
||||
me->SetFacingTo(OnyxiaMoveData[_currentWP].O);
|
||||
DoCastAOE(OnyxiaMoveData[_currentWP].SpellId);
|
||||
events.ScheduleEvent(EVENT_SPELL_BREATH, 8250ms);
|
||||
break;
|
||||
}
|
||||
case EVENT_SPELL_BREATH:
|
||||
{
|
||||
uint8 newWP = OnyxiaMoveData[CurrentWP].DestId;
|
||||
uint8 newWP = OnyxiaMoveData[_currentWP].DestId;
|
||||
me->SetSpeed(MOVE_RUN, 2.95f, false);
|
||||
me->GetMotionMaster()->MovePoint(newWP, OnyxiaMoveData[newWP].x, OnyxiaMoveData[newWP].y, OnyxiaMoveData[newWP].z);
|
||||
me->GetMotionMaster()->MovePoint(newWP, OnyxiaMoveData[newWP].X, OnyxiaMoveData[newWP].Y, OnyxiaMoveData[newWP].Z);
|
||||
break;
|
||||
}
|
||||
case EVENT_START_PHASE_3:
|
||||
{
|
||||
me->SetSpeed(MOVE_RUN, 2.95f, false);
|
||||
me->GetMotionMaster()->MovePoint(12, OnyxiaMoveData[1].x, OnyxiaMoveData[1].y, OnyxiaMoveData[1].z);
|
||||
me->GetMotionMaster()->MovePoint(POINT_PRE_LAND, OnyxiaMoveData[1].X, OnyxiaMoveData[1].Y, OnyxiaMoveData[1].Z);
|
||||
break;
|
||||
}
|
||||
case EVENT_PHASE_3_ATTACK:
|
||||
@@ -509,40 +528,45 @@ public:
|
||||
}
|
||||
case EVENT_ERUPTION:
|
||||
{
|
||||
if (Creature* trigger = me->SummonCreature(12758, *me, TEMPSUMMON_TIMED_DESPAWN, 1000))
|
||||
if (Creature* trigger = me->SummonCreature(NPC_ONYXIA_TRIGGER, *me, TEMPSUMMON_TIMED_DESPAWN, 1000))
|
||||
{
|
||||
trigger->CastSpell(trigger, 17731, false);
|
||||
trigger->CastSpell(trigger, SPELL_ERUPTION, false);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case EVENT_SUMMON_WHELP:
|
||||
{
|
||||
float angle = rand_norm() * 2 * M_PI;
|
||||
float dist = rand_norm() * 4.0f;
|
||||
me->CastSpell(-33.18f + cos(angle) * dist, -258.80f + std::sin(angle) * dist, -89.0f, 17646, true);
|
||||
me->CastSpell(-32.535f + cos(angle) * dist, -170.190f + std::sin(angle) * dist, -89.0f, 17646, true);
|
||||
SummonWhelps();
|
||||
events.Repeat(30s);
|
||||
break;
|
||||
}
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
DoMeleeAttackIfReady();
|
||||
}
|
||||
|
||||
void SpellHitTarget(Unit* target, SpellInfo const* spell) override
|
||||
{
|
||||
if (target->IsPlayer() && spell->DurationEntry && spell->DurationEntry->ID == 328 && spell->Effects[EFFECT_1].TargetA.GetTarget() == 1 && (spell->Effects[EFFECT_1].Amplitude == 50 || spell->Effects[EFFECT_1].Amplitude == 215)) // Deep Breath
|
||||
// Deep Breath is a chain of dozens of triggered spells with no shared id,
|
||||
// so identify a hit by the shape common to all of them
|
||||
if (target->IsPlayer() && spell->DurationEntry && spell->DurationEntry->ID == 328
|
||||
&& spell->Effects[EFFECT_1].TargetA.GetTarget() == TARGET_UNIT_CASTER
|
||||
&& (spell->Effects[EFFECT_1].Amplitude == 50 || spell->Effects[EFFECT_1].Amplitude == 215))
|
||||
{
|
||||
instance->SetData(DATA_DEEP_BREATH_FAILED, 1);
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
uint8 Phase;
|
||||
int8 CurrentWP;
|
||||
uint8 _phase;
|
||||
int8 _currentWP;
|
||||
|
||||
bool whelpSpam;
|
||||
uint8 whelpCount;
|
||||
int32 whelpSpamTimer;
|
||||
bool bManyWhelpsAvailable;
|
||||
bool _whelpSpam;
|
||||
uint8 _whelpCount;
|
||||
int32 _whelpSpamTimer;
|
||||
bool _manyWhelpsAvailable;
|
||||
};
|
||||
|
||||
struct npc_onyxian_lair_guard : public ScriptedAI
|
||||
@@ -590,6 +614,8 @@ public:
|
||||
events.Repeat(18s, 21s);
|
||||
}
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
if (!me->HasUnitState(UNIT_STATE_CASTING) && me->isAttackReady())
|
||||
|
||||
@@ -60,7 +60,7 @@ public:
|
||||
go->CastSpell((Unit*)nullptr, 17646);
|
||||
if (Creature* onyxia = GetCreature(DATA_ONYXIA))
|
||||
{
|
||||
onyxia->AI()->DoAction(-1);
|
||||
onyxia->AI()->DoAction(ACTION_WHELP_SUMMONED);
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -36,9 +36,15 @@ enum eCreatures
|
||||
{
|
||||
NPC_ONYXIA = 10184,
|
||||
NPC_ONYXIAN_WHELP = 11262,
|
||||
NPC_ONYXIA_TRIGGER = 12758,
|
||||
NPC_ONYXIAN_LAIR_GUARD = 36561,
|
||||
};
|
||||
|
||||
enum eActions
|
||||
{
|
||||
ACTION_WHELP_SUMMONED = -1
|
||||
};
|
||||
|
||||
enum eGameObjects
|
||||
{
|
||||
GO_WHELP_SPAWNER = 176510,
|
||||
|
||||
@@ -16,6 +16,7 @@
|
||||
*/
|
||||
|
||||
#include "CreatureScript.h"
|
||||
#include "Player.h"
|
||||
#include "ScriptedCreature.h"
|
||||
#include "SpellScript.h"
|
||||
#include "SpellScriptLoader.h"
|
||||
@@ -142,7 +143,7 @@ struct boss_skeram : public BossAI
|
||||
me->RemoveCorpse();
|
||||
}
|
||||
|
||||
void JustEngagedWith(Unit* /*who*/) override
|
||||
void JustEngagedWith(Unit* who) override
|
||||
{
|
||||
_JustEngagedWith();
|
||||
events.Reset();
|
||||
@@ -154,7 +155,9 @@ struct boss_skeram : public BossAI
|
||||
|
||||
if (!me->IsSummon())
|
||||
{
|
||||
Talk(SAY_AGGRO);
|
||||
// Resolve pets/guardians to their owner so gendered locales resolve $g against the puller
|
||||
Unit* puller = who->GetCharmerOrOwnerPlayerOrPlayerItself();
|
||||
Talk(SAY_AGGRO, puller ? puller : who);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -23,12 +23,13 @@
|
||||
## npc_webbed_creature
|
||||
######*/
|
||||
|
||||
//possible creatures to be spawned
|
||||
uint32 const possibleSpawns[32] = {17322, 17661, 17496, 17522, 17340, 17352, 17333, 17524, 17654, 17348, 17339, 17345, 17353, 17336, 17550, 17330, 17701, 17321, 17325, 17320, 17683, 17342, 17715, 17334, 17341, 17338, 17337, 17346, 17344, 17327};
|
||||
|
||||
enum WebbedCreature
|
||||
{
|
||||
NPC_EXPEDITION_RESEARCHER = 17681
|
||||
SPELL_FREE_WEBBED_CREATURE_HOSTILE_START = 30954,
|
||||
SPELL_FREE_WEBBED_CREATURE_HOSTILE_END = 30963,
|
||||
SPELL_FREE_WEBBED_CREATURE_RESEARCHER = 31010,
|
||||
|
||||
NPC_EXPEDITION_RESEARCHER = 17681
|
||||
};
|
||||
|
||||
class npc_webbed_creature : public CreatureScript
|
||||
@@ -44,39 +45,20 @@ public:
|
||||
|
||||
void JustEngagedWith(Unit* /*who*/) override { }
|
||||
|
||||
void JustDied(Unit* killer) override
|
||||
void JustDied(Unit* /*killer*/) override
|
||||
{
|
||||
uint32 spawnCreatureID = 0;
|
||||
|
||||
switch (urand(0, 2))
|
||||
{
|
||||
case 0:
|
||||
if (Player* player = killer->ToPlayer())
|
||||
{
|
||||
player->KilledMonsterCredit(NPC_EXPEDITION_RESEARCHER);
|
||||
}
|
||||
else if (killer->IsPet())
|
||||
{
|
||||
if (Unit* owner = killer->GetOwner())
|
||||
{
|
||||
if (owner->IsPlayer())
|
||||
{
|
||||
owner->ToPlayer()->KilledMonsterCredit(NPC_EXPEDITION_RESEARCHER);
|
||||
}
|
||||
}
|
||||
}
|
||||
spawnCreatureID = NPC_EXPEDITION_RESEARCHER;
|
||||
me->CastSpell(me, SPELL_FREE_WEBBED_CREATURE_RESEARCHER, true);
|
||||
if (Player* player = me->GetLootRecipient())
|
||||
player->RewardPlayerAndGroupAtEvent(NPC_EXPEDITION_RESEARCHER, player);
|
||||
break;
|
||||
case 1:
|
||||
case 2:
|
||||
spawnCreatureID = possibleSpawns[urand(0, 30)];
|
||||
me->CastSpell(me, urand(SPELL_FREE_WEBBED_CREATURE_HOSTILE_START, SPELL_FREE_WEBBED_CREATURE_HOSTILE_END), true);
|
||||
break;
|
||||
}
|
||||
|
||||
if (spawnCreatureID)
|
||||
{
|
||||
me->SummonCreature(spawnCreatureID, 0.0f, 0.0f, 0.0f, me->GetOrientation(), TEMPSUMMON_TIMED_DESPAWN_OUT_OF_COMBAT, 60000);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -84,6 +84,7 @@ enum Spells
|
||||
SPELL_SARTHARION_FLAME_BREATH = 56908,
|
||||
SPELL_SARTHARION_TAIL_LASH = 56910,
|
||||
SPELL_CYCLONE_AURA_PERIODIC = 57598,
|
||||
SPELL_LAVA_STRIKE = 57571,
|
||||
SPELL_LAVA_STRIKE_DUMMY = 57578,
|
||||
SPELL_LAVA_STRIKE_DUMMY_TRIGGER = 57697,
|
||||
SPELL_LAVA_STRIKE_SUMMON = 57572,
|
||||
@@ -213,11 +214,11 @@ const Position TenebronEggsPos[2][MAX_TENEBORN_EGGS_SUMMONS] =
|
||||
|
||||
const Position CycloneSummonPos[MAX_CYCLONE_COUNT] =
|
||||
{
|
||||
{ 3235.28f, 591.180f, 57.0833f, 0.59037f },
|
||||
{ 3200.97f, 480.929f, 57.0833f, 5.86197f },
|
||||
{ 3281.57f, 507.984f, 57.0833f, 5.54346f },
|
||||
{ 3210.11f, 531.957f, 57.0833f, 3.76777f },
|
||||
{ 3286.42f, 585.010f, 57.0833f, 4.10307f },
|
||||
{ 3238.55f, 589.14f, 57.0f, 0.59037f },
|
||||
{ 3209.70f, 475.85f, 57.0f, 5.86197f },
|
||||
{ 3282.10f, 504.02f, 57.0f, 5.54346f },
|
||||
{ 3209.42f, 532.55f, 57.0f, 3.76777f },
|
||||
{ 3283.50f, 581.75f, 57.0f, 4.10307f },
|
||||
};
|
||||
|
||||
const Position AreaTriggerSummonPos[MAX_AREA_TRIGGER_COUNT] =
|
||||
@@ -1380,20 +1381,23 @@ class spell_sartharion_lava_strike : public SpellScript
|
||||
bool Load() override
|
||||
{
|
||||
_spawned = false;
|
||||
_dummyFired = false;
|
||||
return true;
|
||||
}
|
||||
|
||||
void HandleDummy(SpellEffIndex /*effIndex*/)
|
||||
{
|
||||
if (!GetCaster() || !GetHitUnit())
|
||||
if (!GetCaster() || !GetHitUnit() || _dummyFired)
|
||||
return;
|
||||
|
||||
GetCaster()->CastSpell(GetHitUnit()->GetPositionX(), GetHitUnit()->GetPositionY(), GetHitUnit()->GetPositionZ(), SPELL_LAVA_STRIKE_DUMMY_TRIGGER, true);
|
||||
_dummyFired = true;
|
||||
|
||||
GetCaster()->CastSpell(GetHitUnit(), SPELL_LAVA_STRIKE, true);
|
||||
}
|
||||
|
||||
void HandleSchoolDamage(SpellEffIndex /*effIndex*/)
|
||||
{
|
||||
if (!GetCaster() || !GetHitUnit() || _spawned)
|
||||
if (!GetCaster() || !GetHitUnit() || !GetHitUnit()->IsPlayer() || _spawned)
|
||||
return;
|
||||
|
||||
if (InstanceScript* instance = GetCaster()->GetInstanceScript())
|
||||
@@ -1402,21 +1406,31 @@ class spell_sartharion_lava_strike : public SpellScript
|
||||
{
|
||||
sarth->AI()->SetData(DATA_VOLCANO_BLOWS, GetHitUnit()->GetGUID().GetCounter());
|
||||
sarth->CastSpell(GetHitUnit(), SPELL_LAVA_STRIKE_SUMMON, true);
|
||||
_spawned = true;
|
||||
}
|
||||
}
|
||||
|
||||
_spawned = true;
|
||||
}
|
||||
|
||||
void HandleSummon(SpellEffIndex effIndex)
|
||||
{
|
||||
if (GetCaster()->GetEntry() != NPC_SARTHARION)
|
||||
PreventHitEffect(effIndex);
|
||||
}
|
||||
|
||||
void Register() override
|
||||
{
|
||||
if (m_scriptSpellId == SPELL_LAVA_STRIKE_DUMMY)
|
||||
OnEffectHitTarget += SpellEffectFn(spell_sartharion_lava_strike::HandleDummy, EFFECT_0, SPELL_EFFECT_DUMMY);
|
||||
else if (m_scriptSpellId == SPELL_LAVA_STRIKE_SUMMON)
|
||||
OnEffectHit += SpellEffectFn(spell_sartharion_lava_strike::HandleSummon, EFFECT_0, SPELL_EFFECT_SUMMON);
|
||||
else
|
||||
OnEffectHitTarget += SpellEffectFn(spell_sartharion_lava_strike::HandleSchoolDamage, EFFECT_0, SPELL_EFFECT_SCHOOL_DAMAGE);
|
||||
}
|
||||
|
||||
private:
|
||||
bool _spawned{false};
|
||||
bool _dummyFired{false};
|
||||
};
|
||||
|
||||
// 57491 - Flame Tsunami
|
||||
|
||||
@@ -107,17 +107,19 @@ public:
|
||||
SlugeCount = 0;
|
||||
instance->SetData(DATA_SJONNIR_ACHIEVEMENT, false);
|
||||
|
||||
if (instance->GetData(BOSS_TRIBUNAL_OF_AGES) == DONE)
|
||||
if (instance && instance->GetBossState(BOSS_TRIBUNAL_OF_AGES) == DONE)
|
||||
{
|
||||
if (GameObject* console = me->GetMap()->GetGameObject(instance->GetGuidData(GO_SJONNIR_CONSOLE)))
|
||||
console->SetGoState(GO_STATE_READY);
|
||||
|
||||
if (Creature* brann = ObjectAccessor::GetCreature(*me, instance->GetGuidData(NPC_BRANN)))
|
||||
{
|
||||
brann->setDeathState(DeathState::JustDied);
|
||||
brann->Respawn();
|
||||
brann->AI()->DoAction(ACTION_SJONNIR_WIPE_START);
|
||||
}
|
||||
}
|
||||
|
||||
if (instance && instance->GetBossState(BRANN_DOOR) == DONE)
|
||||
{
|
||||
if (GameObject* doors = me->GetMap()->GetGameObject(instance->GetGuidData(GO_SJONNIR_DOOR)))
|
||||
doors->SetGoState(GO_STATE_ACTIVE);
|
||||
}
|
||||
|
||||
ScheduleHealthCheckEvent(75, [&] {
|
||||
@@ -220,7 +222,7 @@ public:
|
||||
if (GameObject* doors = me->GetMap()->GetGameObject(instance->GetGuidData(GO_SJONNIR_DOOR)))
|
||||
doors->SetGoState(GO_STATE_READY);
|
||||
|
||||
if (instance->GetData(BOSS_TRIBUNAL_OF_AGES) == DONE)
|
||||
if (instance && instance->GetBossState(BOSS_TRIBUNAL_OF_AGES) == DONE)
|
||||
if (Creature* brann = ObjectAccessor::GetCreature(*me, instance->GetGuidData(NPC_BRANN)))
|
||||
brann->AI()->DoAction(ACTION_START_SJONNIR_FIGHT);
|
||||
}
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -36,6 +36,26 @@ enum Texts
|
||||
SAY_BRANN_SPAWN_OOZE = 7,
|
||||
SAY_BRANN_SPAWN_EARTHEN = 8,
|
||||
SAY_BRANN_EVENT_INTRO_1 = 9,
|
||||
SAY_BRANN_EVENT_INTRO_2 = 10,
|
||||
SAY_BRANN_EVENT_A_1 = 11,
|
||||
SAY_BRANN_EVENT_A_3 = 12,
|
||||
SAY_BRANN_EVENT_B_1 = 13,
|
||||
SAY_BRANN_EVENT_B_3 = 14,
|
||||
SAY_BRANN_EVENT_C_1 = 15,
|
||||
SAY_BRANN_EVENT_C_3 = 16,
|
||||
SAY_BRANN_EVENT_D_1 = 17,
|
||||
SAY_BRANN_EVENT_D_3 = 18,
|
||||
SAY_BRANN_EVENT_END_01 = 19,
|
||||
SAY_BRANN_EVENT_END_02 = 20,
|
||||
SAY_BRANN_EVENT_END_04 = 21,
|
||||
SAY_BRANN_EVENT_END_06 = 22,
|
||||
SAY_BRANN_EVENT_END_08 = 23,
|
||||
SAY_BRANN_EVENT_END_10 = 24,
|
||||
SAY_BRANN_EVENT_END_12 = 25,
|
||||
SAY_BRANN_EVENT_END_14 = 26,
|
||||
SAY_BRANN_EVENT_END_16 = 27,
|
||||
SAY_BRANN_EVENT_END_18 = 28,
|
||||
SAY_BRANN_EVENT_END_20 = 29,
|
||||
SAY_BRANN_VICTORY_SJONNIR_1 = 30,
|
||||
SAY_BRANN_VICTORY_SJONNIR_2 = 31,
|
||||
SAY_BRANN_ENTRANCE_MEET = 32,
|
||||
@@ -47,8 +67,8 @@ enum Encounter
|
||||
BOSS_MAIDEN_OF_GRIEF = 1,
|
||||
BOSS_TRIBUNAL_OF_AGES = 2,
|
||||
BOSS_SJONNIR = 3,
|
||||
BRANN_BRONZEBEARD = 4,
|
||||
BRANN_DOOR = 5,
|
||||
BRANN_BRONZEBEARD = 4, // Escort Event
|
||||
BRANN_DOOR = 5, // Sjonnir's Door
|
||||
MAX_ENCOUNTER = 6,
|
||||
|
||||
DATA_BRANN_ACHIEVEMENT,
|
||||
@@ -82,12 +102,13 @@ enum npcs
|
||||
|
||||
ACTION_START_ESCORT_EVENT = 0,
|
||||
ACTION_START_TRIBUNAL = 1,
|
||||
ACTION_TRIBUNAL_WIPE_START = 2,
|
||||
ACTION_GO_TO_SJONNIR = 3,
|
||||
ACTION_OPEN_DOOR = 4,
|
||||
ACTION_START_SJONNIR_FIGHT = 5,
|
||||
ACTION_SJONNIR_DEAD = 6,
|
||||
ACTION_SJONNIR_WIPE_START = 7,
|
||||
ACTION_GO_TO_SJONNIR = 2,
|
||||
ACTION_OPEN_DOOR = 3,
|
||||
ACTION_START_SJONNIR_FIGHT = 4,
|
||||
ACTION_SJONNIR_DEAD = 5,
|
||||
ACTION_SJONNIR_WIPE_START = 6,
|
||||
ACTION_PLAYER_DEATH_IN_TRIBUNAL = 7,
|
||||
ACTION_SKIP_PHASE = 8,
|
||||
};
|
||||
|
||||
template <class AI, class T>
|
||||
|
||||
@@ -90,7 +90,14 @@ public:
|
||||
{
|
||||
for (uint8 i = 0; i < MAX_ENCOUNTER; ++i)
|
||||
{
|
||||
if (Encounter[i] == IN_PROGRESS && i != BRANN_BRONZEBEARD)
|
||||
// The escort is not an encounter, it must not keep the instance locked
|
||||
if (i == BRANN_BRONZEBEARD)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
// Krystallus and the Maiden of Grief are tracked through SetData, the rest through boss states
|
||||
if (Encounter[i] == IN_PROGRESS || GetBossState(i) == IN_PROGRESS)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
@@ -98,6 +105,15 @@ public:
|
||||
return false;
|
||||
}
|
||||
|
||||
void OnUnitDeath(Unit* unit) override
|
||||
{
|
||||
if (unit->IsPlayer() && GetBossState(BOSS_TRIBUNAL_OF_AGES) == IN_PROGRESS)
|
||||
{
|
||||
if (Creature* brann = instance->GetCreature(GetGuidData(NPC_BRANN)))
|
||||
brann->AI()->DoAction(ACTION_PLAYER_DEATH_IN_TRIBUNAL);
|
||||
}
|
||||
}
|
||||
|
||||
void OnGameObjectCreate(GameObject* go) override
|
||||
{
|
||||
switch (go->GetEntry())
|
||||
@@ -107,7 +123,8 @@ public:
|
||||
break;
|
||||
case GO_ABEDNEUM:
|
||||
goAbedneumGUID = go->GetGUID();
|
||||
if (Encounter[BOSS_TRIBUNAL_OF_AGES] == DONE)
|
||||
// Encounter[] is toggled back and forth by the post event lore, the boss state is the reliable one
|
||||
if (GetBossState(BOSS_TRIBUNAL_OF_AGES) == DONE)
|
||||
go->SetGoState(GO_STATE_ACTIVE);
|
||||
break;
|
||||
case GO_MARNAK:
|
||||
@@ -118,7 +135,7 @@ public:
|
||||
break;
|
||||
case GO_SKY_FLOOR:
|
||||
goSkyRoomFloorGUID = go->GetGUID();
|
||||
if (Encounter[BOSS_TRIBUNAL_OF_AGES] == DONE)
|
||||
if (GetBossState(BOSS_TRIBUNAL_OF_AGES) == DONE)
|
||||
go->SetGoState(GO_STATE_ACTIVE);
|
||||
break;
|
||||
case GO_SJONNIR_CONSOLE:
|
||||
@@ -126,7 +143,7 @@ public:
|
||||
break;
|
||||
case GO_SJONNIR_DOOR:
|
||||
goSjonnirDoorGUID = go->GetGUID();
|
||||
if (Encounter[BOSS_TRIBUNAL_OF_AGES] == DONE)
|
||||
if (GetBossState(BRANN_DOOR) == DONE)
|
||||
go->SetGoState(GO_STATE_ACTIVE);
|
||||
break;
|
||||
case GO_LEFT_PIPE:
|
||||
@@ -146,7 +163,6 @@ public:
|
||||
BrannGUID = creature->GetGUID();
|
||||
break;
|
||||
}
|
||||
|
||||
InstanceScript::OnCreatureCreate(creature);
|
||||
}
|
||||
|
||||
@@ -191,6 +207,7 @@ public:
|
||||
case BOSS_TRIBUNAL_OF_AGES:
|
||||
case BOSS_SJONNIR:
|
||||
case BRANN_BRONZEBEARD:
|
||||
case BRANN_DOOR:
|
||||
return Encounter[id];
|
||||
}
|
||||
|
||||
@@ -308,6 +325,12 @@ public:
|
||||
pSkyRoomFloor->SetGoState(GO_STATE_READY);
|
||||
}
|
||||
|
||||
if (type == BRANN_DOOR && data == DONE)
|
||||
{
|
||||
if (GameObject* pSjonnirDoor = instance->GetGameObject(goSjonnirDoorGUID))
|
||||
pSjonnirDoor->SetGoState(GO_STATE_ACTIVE);
|
||||
}
|
||||
|
||||
if (type == DATA_BRANN_ACHIEVEMENT)
|
||||
{
|
||||
brannAchievement = (bool)data;
|
||||
@@ -330,6 +353,7 @@ public:
|
||||
data >> Encounter[2];
|
||||
data >> Encounter[3];
|
||||
data >> Encounter[4];
|
||||
data >> Encounter[5];
|
||||
}
|
||||
|
||||
void WriteSaveDataMore(std::ostringstream& data) override
|
||||
@@ -338,7 +362,8 @@ public:
|
||||
<< Encounter[1] << ' '
|
||||
<< Encounter[2] << ' '
|
||||
<< Encounter[3] << ' '
|
||||
<< Encounter[4] << ' ';
|
||||
<< Encounter[4] << ' '
|
||||
<< Encounter[5] << ' ';
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
@@ -25,6 +25,7 @@
|
||||
#include "PassiveAI.h"
|
||||
#include "Player.h"
|
||||
#include "ScriptedCreature.h"
|
||||
#include "ScriptMgr.h"
|
||||
#include "SpellScript.h"
|
||||
#include "SpellScriptLoader.h"
|
||||
#include "ulduar.h"
|
||||
@@ -78,7 +79,7 @@ enum Actions
|
||||
{
|
||||
//ACTION_INIT_ALGALON = 1, defined in ulduar.h
|
||||
//ACTION_DESPAWN_ALGALON = 2, defined in ulduar.h
|
||||
ACTION_START_INTRO = 3,
|
||||
//ACTION_START_INTRO = 3, defined in ulduar.h
|
||||
ACTION_FINISH_INTRO = 4,
|
||||
ACTION_ACTIVATE_STAR = 5,
|
||||
ACTION_BIG_BANG = 6,
|
||||
@@ -95,6 +96,7 @@ enum Misc
|
||||
|
||||
POINT_ALGALON_LAND = 1,
|
||||
POINT_ALGALON_OUTRO = 2,
|
||||
POINT_ALGALON_FLOAT = 3,
|
||||
|
||||
EVENT_ID_SUPERMASSIVE_START = 21697,
|
||||
|
||||
@@ -112,46 +114,51 @@ enum Events
|
||||
EVENT_SUMMON_ALGALON = 3,
|
||||
EVENT_BRANN_OUTRO_1 = 4,
|
||||
EVENT_BRANN_OUTRO_2 = 5,
|
||||
EVENT_BRANN_REACH_TALK_POINT = 6,
|
||||
|
||||
// Algalon the Observer
|
||||
EVENT_INTRO_1 = 6,
|
||||
EVENT_INTRO_2 = 7,
|
||||
EVENT_INTRO_3 = 8,
|
||||
EVENT_INTRO_FINISH = 9,
|
||||
EVENT_SAY_ALGALON_AGGRO = 10,
|
||||
EVENT_INTRO_TIMER_DONE = 11,
|
||||
EVENT_QUANTUM_STRIKE = 12,
|
||||
EVENT_PHASE_PUNCH = 13,
|
||||
EVENT_SUMMON_COLLAPSING_STAR = 14,
|
||||
EVENT_BIG_BANG = 15,
|
||||
EVENT_RESUME_UPDATING = 16,
|
||||
EVENT_ASCEND_TO_THE_HEAVENS = 17,
|
||||
EVENT_EVADE = 18,
|
||||
EVENT_COSMIC_SMASH = 19,
|
||||
EVENT_UNLOCK_YELL = 20,
|
||||
EVENT_OUTRO_START = 21,
|
||||
EVENT_OUTRO_1 = 22,
|
||||
EVENT_OUTRO_2 = 23,
|
||||
EVENT_OUTRO_3 = 24,
|
||||
EVENT_OUTRO_4 = 25,
|
||||
EVENT_OUTRO_5 = 26,
|
||||
EVENT_OUTRO_6 = 27,
|
||||
EVENT_OUTRO_7 = 28,
|
||||
EVENT_OUTRO_8 = 29,
|
||||
EVENT_OUTRO_9 = 30,
|
||||
EVENT_OUTRO_10 = 31,
|
||||
EVENT_OUTRO_11 = 32,
|
||||
EVENT_ACTIVATE_LIVING_CONSTELLATION = 33,
|
||||
EVENT_CHECK_HERALD_ITEMS = 34,
|
||||
EVENT_ALGALON_IN_PROGRESS = 35,
|
||||
EVENT_DESPAWN_ALGALON_1 = 36,
|
||||
EVENT_DESPAWN_ALGALON_2 = 37,
|
||||
EVENT_DESPAWN_ALGALON_3 = 38,
|
||||
EVENT_DESPAWN_ALGALON_4 = 39,
|
||||
EVENT_DESPAWN_ALGALON_5 = 40,
|
||||
EVENT_INTRO_1 = 7,
|
||||
EVENT_INTRO_2 = 8,
|
||||
EVENT_INTRO_3 = 9,
|
||||
EVENT_INTRO_FINISH = 10,
|
||||
EVENT_SAY_ALGALON_AGGRO = 11,
|
||||
EVENT_INTRO_TIMER_DONE = 12,
|
||||
EVENT_QUANTUM_STRIKE = 13,
|
||||
EVENT_PHASE_PUNCH = 14,
|
||||
EVENT_SUMMON_COLLAPSING_STAR = 15,
|
||||
EVENT_BIG_BANG = 16,
|
||||
EVENT_RESUME_UPDATING = 17,
|
||||
EVENT_ASCEND_TO_THE_HEAVENS = 18,
|
||||
EVENT_EVADE = 19,
|
||||
EVENT_COSMIC_SMASH = 20,
|
||||
EVENT_UNLOCK_YELL = 21,
|
||||
EVENT_OUTRO_START = 22,
|
||||
EVENT_OUTRO_1 = 23,
|
||||
EVENT_OUTRO_2 = 24,
|
||||
EVENT_OUTRO_3 = 25,
|
||||
EVENT_OUTRO_4 = 26,
|
||||
EVENT_OUTRO_5 = 27,
|
||||
EVENT_OUTRO_6 = 28,
|
||||
EVENT_OUTRO_7 = 29,
|
||||
EVENT_OUTRO_8 = 30,
|
||||
EVENT_OUTRO_9 = 31,
|
||||
EVENT_OUTRO_10 = 32,
|
||||
EVENT_OUTRO_11 = 33,
|
||||
EVENT_ACTIVATE_LIVING_CONSTELLATION = 34,
|
||||
EVENT_CHECK_HERALD_ITEMS = 35,
|
||||
EVENT_ALGALON_IN_PROGRESS = 36,
|
||||
EVENT_DESPAWN_ALGALON_1 = 37,
|
||||
EVENT_DESPAWN_ALGALON_2 = 38,
|
||||
EVENT_DESPAWN_ALGALON_3 = 39,
|
||||
EVENT_DESPAWN_ALGALON_4 = 40,
|
||||
EVENT_DESPAWN_ALGALON_5 = 41,
|
||||
|
||||
EVENT_INTRO_CHANNEL = 42,
|
||||
EVENT_INTRO_SUMMON = 43,
|
||||
EVENT_INTRO_DESCEND = 44,
|
||||
|
||||
// Living Constellation
|
||||
EVENT_ARCANE_BARRAGE = 41,
|
||||
EVENT_ARCANE_BARRAGE = 45,
|
||||
};
|
||||
|
||||
enum EncounterPhases
|
||||
@@ -204,7 +211,7 @@ Position const BrannIntroWaypoint[MAX_BRANN_WAYPOINTS_INTRO] =
|
||||
{1632.676f, -190.5927f, 427.2631f, 0.0f},
|
||||
{1631.497f, -214.2221f, 418.1152f, 0.0f},
|
||||
{1636.455f, -263.6647f, 417.3213f, 0.0f},
|
||||
{1629.586f, -267.9792f, 417.3219f, 0.0f},
|
||||
{1624.1223f, -267.04172f, 417.3216f, 4.7690268f},
|
||||
{1631.497f, -214.2221f, 418.1152f, 0.0f},
|
||||
{1632.676f, -190.5927f, 425.8831f, 0.0f},
|
||||
{1632.814f, -173.9334f, 427.2621f, 0.0f},
|
||||
@@ -235,7 +242,8 @@ Position const CollapsingStarPos[COLLAPSING_STAR_COUNT] =
|
||||
{1622.451f, -321.1563f, 417.6188f, 4.677482f},
|
||||
{1615.060f, -291.6816f, 417.7796f, 3.490659f},
|
||||
};
|
||||
Position const AlgalonOutroPos = {1633.64f, -317.78f, 417.3211f, 0.0f};
|
||||
Position const AlgalonOutroPos = {1633.64f, -317.78f, 417.3211f, 1.605703f};
|
||||
Position const AlgalonFloatPos = {1632.668f, -302.7656f, 420.3211f, 1.530165f};
|
||||
Position const BrannOutroPos[3] =
|
||||
{
|
||||
{1632.023f, -243.7434f, 417.9118f, 0.0f},
|
||||
@@ -369,7 +377,14 @@ struct boss_algalon_the_observer : public ScriptedAI
|
||||
if (_instance)
|
||||
_instance->SetBossState(BOSS_ALGALON, FAIL);
|
||||
|
||||
ScriptedAI::EnterEvadeMode(why);
|
||||
if (!_EnterEvadeMode(why))
|
||||
return;
|
||||
|
||||
me->GetMotionMaster()->MoveTargetedHome();
|
||||
|
||||
Reset();
|
||||
|
||||
sScriptMgr->OnUnitEnterEvadeMode(me, why);
|
||||
}
|
||||
|
||||
void Reset() override
|
||||
@@ -392,6 +407,8 @@ struct boss_algalon_the_observer : public ScriptedAI
|
||||
{
|
||||
_firstPull = false;
|
||||
_instance->StorePersistentData(PERSISTENT_DATA_ALGALON_FIRST_PULL, 1);
|
||||
_instance->SetData(DATA_RESUMMON_ALGALON, 0);
|
||||
me->DespawnOrUnsummon(1ms);
|
||||
}
|
||||
_instance->SetBossState(BOSS_ALGALON, NOT_STARTED);
|
||||
}
|
||||
@@ -416,15 +433,14 @@ struct boss_algalon_the_observer : public ScriptedAI
|
||||
me->SetDisableGravity(true);
|
||||
me->CastSpell(me, SPELL_ARRIVAL, true);
|
||||
me->CastSpell(me, SPELL_RIDE_THE_LIGHTNING, true);
|
||||
me->GetMotionMaster()->MovePoint(POINT_ALGALON_LAND, AlgalonLandPos);
|
||||
me->GetMotionMaster()->MovePoint(POINT_ALGALON_FLOAT, AlgalonFloatPos);
|
||||
me->SetHomePosition(AlgalonLandPos);
|
||||
Movement::MoveSplineInit init(me);
|
||||
init.MoveTo(AlgalonLandPos.GetPositionX(), AlgalonLandPos.GetPositionY(), AlgalonLandPos.GetPositionZ());
|
||||
init.SetOrientationFixed(true);
|
||||
init.Launch();
|
||||
events.Reset();
|
||||
events.SetPhase(PHASE_ROLE_PLAY);
|
||||
events.ScheduleEvent(EVENT_INTRO_1, 5s, 0, PHASE_ROLE_PLAY);
|
||||
events.ScheduleEvent(EVENT_INTRO_CHANNEL, 6s, 0, PHASE_ROLE_PLAY);
|
||||
events.ScheduleEvent(EVENT_INTRO_SUMMON, 7s, 0, PHASE_ROLE_PLAY);
|
||||
events.ScheduleEvent(EVENT_INTRO_DESCEND, 10s, 0, PHASE_ROLE_PLAY);
|
||||
events.ScheduleEvent(EVENT_INTRO_2, 15s, 0, PHASE_ROLE_PLAY);
|
||||
events.ScheduleEvent(EVENT_INTRO_3, 23s, 0, PHASE_ROLE_PLAY);
|
||||
events.ScheduleEvent(EVENT_INTRO_FINISH, 36s, 0, PHASE_ROLE_PLAY);
|
||||
@@ -525,18 +541,7 @@ struct boss_algalon_the_observer : public ScriptedAI
|
||||
if (pointId == POINT_ALGALON_LAND)
|
||||
me->SetDisableGravity(false);
|
||||
else if (pointId == POINT_ALGALON_OUTRO)
|
||||
{
|
||||
me->SetFacingTo(1.605703f);
|
||||
events.ScheduleEvent(EVENT_OUTRO_3, 1200ms);
|
||||
events.ScheduleEvent(EVENT_OUTRO_4, 2400ms);
|
||||
events.ScheduleEvent(EVENT_OUTRO_5, 8500ms);
|
||||
events.ScheduleEvent(EVENT_OUTRO_6, 15s + 500ms);
|
||||
events.ScheduleEvent(EVENT_OUTRO_7, 55s + 500ms);
|
||||
events.ScheduleEvent(EVENT_OUTRO_8, 73s + 500ms);
|
||||
events.ScheduleEvent(EVENT_OUTRO_9, 85s + 500ms);
|
||||
events.ScheduleEvent(EVENT_OUTRO_10, 111s);
|
||||
events.ScheduleEvent(EVENT_OUTRO_11, 117s + 500ms);
|
||||
}
|
||||
me->SetFacingTo(AlgalonOutroPos.GetOrientation());
|
||||
}
|
||||
|
||||
void JustSummoned(Creature* summon) override
|
||||
@@ -645,14 +650,26 @@ struct boss_algalon_the_observer : public ScriptedAI
|
||||
{
|
||||
case EVENT_INTRO_1:
|
||||
me->RemoveAurasDueToSpell(SPELL_RIDE_THE_LIGHTNING);
|
||||
Talk(SAY_ALGALON_INTRO_1);
|
||||
if (_firstPull)
|
||||
Talk(SAY_ALGALON_INTRO_1);
|
||||
break;
|
||||
case EVENT_INTRO_CHANNEL:
|
||||
me->SetEmoteState(EMOTE_STATE_SPELL_CHANNEL_OMNI);
|
||||
break;
|
||||
case EVENT_INTRO_SUMMON:
|
||||
me->CastSpell((Unit*)nullptr, SPELL_SUMMON_AZEROTH, true);
|
||||
me->ClearEmoteState();
|
||||
break;
|
||||
case EVENT_INTRO_DESCEND:
|
||||
me->GetMotionMaster()->MovePoint(POINT_ALGALON_LAND, AlgalonLandPos);
|
||||
break;
|
||||
case EVENT_INTRO_2:
|
||||
me->CastSpell((Unit*)nullptr, SPELL_SUMMON_AZEROTH, true);
|
||||
Talk(SAY_ALGALON_INTRO_2);
|
||||
if (_firstPull)
|
||||
Talk(SAY_ALGALON_INTRO_2);
|
||||
break;
|
||||
case EVENT_INTRO_3:
|
||||
Talk(SAY_ALGALON_INTRO_3);
|
||||
if (_firstPull)
|
||||
Talk(SAY_ALGALON_INTRO_3);
|
||||
break;
|
||||
case EVENT_INTRO_FINISH:
|
||||
events.Reset();
|
||||
@@ -755,9 +772,20 @@ struct boss_algalon_the_observer : public ScriptedAI
|
||||
{
|
||||
Player* lootRecipent = me->GetLootRecipient();
|
||||
_EnterEvadeMode();
|
||||
me->ClearUnitState(UNIT_STATE_EVADE);
|
||||
// LootRecipent is cleared in _EnterEvadeMode, restore it
|
||||
me->SetLootRecipient(lootRecipent);
|
||||
me->GetMotionMaster()->Clear(false);
|
||||
me->GetMotionMaster()->MovePoint(POINT_ALGALON_OUTRO, AlgalonOutroPos);
|
||||
events.ScheduleEvent(EVENT_OUTRO_3, 3200ms);
|
||||
events.ScheduleEvent(EVENT_OUTRO_4, 4400ms);
|
||||
events.ScheduleEvent(EVENT_OUTRO_5, 10500ms);
|
||||
events.ScheduleEvent(EVENT_OUTRO_6, 17s + 500ms);
|
||||
events.ScheduleEvent(EVENT_OUTRO_7, 57s + 500ms);
|
||||
events.ScheduleEvent(EVENT_OUTRO_8, 75s + 500ms);
|
||||
events.ScheduleEvent(EVENT_OUTRO_9, 87s + 500ms);
|
||||
events.ScheduleEvent(EVENT_OUTRO_10, 113s);
|
||||
events.ScheduleEvent(EVENT_OUTRO_11, 119s + 500ms);
|
||||
break;
|
||||
}
|
||||
case EVENT_OUTRO_3:
|
||||
@@ -869,16 +897,12 @@ struct npc_brann_bronzebeard_algalon : public CreatureAI
|
||||
delay = 8s;
|
||||
me->SetWalk(true);
|
||||
break;
|
||||
case 6:
|
||||
me->SetFacingTo(4.6156f);
|
||||
me->SetWalk(false);
|
||||
Talk(SAY_BRANN_ALGALON_INTRO_1);
|
||||
events.ScheduleEvent(EVENT_SUMMON_ALGALON, 7500ms);
|
||||
return;
|
||||
case 10:
|
||||
me->DespawnOrUnsummon(1ms);
|
||||
return;
|
||||
case POINT_BRANN_OUTRO:
|
||||
me->SetFacingTo(4.6528215f);
|
||||
return;
|
||||
case POINT_BRANN_OUTRO_END:
|
||||
return;
|
||||
}
|
||||
@@ -895,7 +919,32 @@ struct npc_brann_bronzebeard_algalon : public CreatureAI
|
||||
{
|
||||
case EVENT_BRANN_MOVE_INTRO:
|
||||
if (_currentPoint < MAX_BRANN_WAYPOINTS_INTRO)
|
||||
me->GetMotionMaster()->MovePoint(_currentPoint, BrannIntroWaypoint[_currentPoint]);
|
||||
{
|
||||
if (_currentPoint == 5 || _currentPoint == 6)
|
||||
{
|
||||
Position const& dest = BrannIntroWaypoint[_currentPoint];
|
||||
Milliseconds travelTime = Milliseconds(uint32(me->GetExactDist2d(dest) / 2.5f * 1000)) + 500ms;
|
||||
Movement::MoveSplineInit init(me);
|
||||
init.MoveTo(dest.GetPositionX(), dest.GetPositionY(), dest.GetPositionZ());
|
||||
init.Launch();
|
||||
if (_currentPoint == 6)
|
||||
events.ScheduleEvent(EVENT_BRANN_REACH_TALK_POINT, travelTime);
|
||||
else
|
||||
{
|
||||
_currentPoint = 6;
|
||||
events.ScheduleEvent(EVENT_BRANN_MOVE_INTRO, travelTime);
|
||||
}
|
||||
}
|
||||
else
|
||||
me->GetMotionMaster()->MovePoint(_currentPoint, BrannIntroWaypoint[_currentPoint]);
|
||||
}
|
||||
break;
|
||||
case EVENT_BRANN_REACH_TALK_POINT:
|
||||
_currentPoint = 7;
|
||||
me->SetFacingTo(4.7760954f);
|
||||
me->SetWalk(false);
|
||||
Talk(SAY_BRANN_ALGALON_INTRO_1);
|
||||
events.ScheduleEvent(EVENT_SUMMON_ALGALON, 7500ms);
|
||||
break;
|
||||
case EVENT_SUMMON_ALGALON:
|
||||
if (me->GetInstanceScript() && !me->GetInstanceScript()->GetCreature(BOSS_ALGALON))
|
||||
|
||||
@@ -26,6 +26,7 @@
|
||||
#include "SpellScript.h"
|
||||
#include "SpellScriptLoader.h"
|
||||
#include "ulduar.h"
|
||||
#include <algorithm>
|
||||
|
||||
enum HodirSpellData
|
||||
{
|
||||
@@ -136,6 +137,11 @@ enum HodirEvents
|
||||
EVENT_MAGE_TOASTY_FIRE = 18,
|
||||
EVENT_MAGE_FIREBALL = 19,
|
||||
EVENT_MAGE_MELT_ICE = 20,
|
||||
|
||||
EVENT_VICTORY_CHEER_2 = 21,
|
||||
EVENT_VICTORY_CHEER_3 = 22,
|
||||
EVENT_VICTORY_DANCE = 23,
|
||||
EVENT_VICTORY_DESPAWN = 24,
|
||||
};
|
||||
|
||||
enum HodirText
|
||||
@@ -162,6 +168,11 @@ enum HodirSounds
|
||||
SOUND_HODIR_BERSERK = 15558,
|
||||
};
|
||||
|
||||
enum HodirHelperActions
|
||||
{
|
||||
ACTION_VICTORY_EMOTE = 1,
|
||||
};
|
||||
|
||||
struct HodirHelperData
|
||||
{
|
||||
uint32 id;
|
||||
@@ -342,7 +353,15 @@ struct boss_hodir : public BossAI
|
||||
me->RemoveAllAuras();
|
||||
|
||||
events.Reset();
|
||||
summons.DespawnAll();
|
||||
|
||||
// Manually despawn helpers
|
||||
summons.DespawnIf([this](ObjectGuid guid) -> bool
|
||||
{
|
||||
return std::ranges::none_of(Helpers, [guid](ObjectGuid Helper) { return guid == Helper; });
|
||||
});
|
||||
|
||||
// Start victory emote sequence on surviving helpers
|
||||
DoHelperVictoryEmotes();
|
||||
|
||||
Talk(TEXT_DEATH);
|
||||
scheduler.Schedule(14s, [this](TaskContext /*context*/)
|
||||
@@ -518,6 +537,36 @@ struct boss_hodir : public BossAI
|
||||
}
|
||||
}
|
||||
|
||||
void DoHelperVictoryEmotes()
|
||||
{
|
||||
for (uint8 i = 0; i < 8; ++i)
|
||||
{
|
||||
Creature* helper = GetHelper(i);
|
||||
if (!helper)
|
||||
continue;
|
||||
|
||||
if (!helper->IsAlive())
|
||||
{
|
||||
helper->DespawnOrUnsummon();
|
||||
continue;
|
||||
}
|
||||
|
||||
// Stop combat behavior and start victory emote chain
|
||||
helper->AI()->DoAction(ACTION_VICTORY_EMOTE);
|
||||
|
||||
// Thaw if still frozen
|
||||
if (helper->HasAura(SPELL_FLASH_FREEZE_TRAPPED_NPC))
|
||||
{
|
||||
helper->RemoveAura(SPELL_FLASH_FREEZE_TRAPPED_NPC);
|
||||
if (Creature* iceBlock = helper->FindNearestCreature(NPC_FLASH_FREEZE_NPC, 5.0f))
|
||||
iceBlock->DespawnOrUnsummon();
|
||||
}
|
||||
|
||||
// First cheer
|
||||
helper->HandleEmoteCommand(EMOTE_ONESHOT_CHEER_NO_SHEATHE);
|
||||
}
|
||||
}
|
||||
|
||||
void KilledUnit(Unit* who) override
|
||||
{
|
||||
if (who->IsPlayer())
|
||||
@@ -753,6 +802,37 @@ struct npc_ulduar_hodir_priest : public ScriptedAI
|
||||
me->CastSpell(victim, SPELL_PRIEST_SMITE, false);
|
||||
events.Repeat(2100ms);
|
||||
break;
|
||||
case EVENT_VICTORY_CHEER_2:
|
||||
if (me->IsAlive())
|
||||
me->HandleEmoteCommand(EMOTE_ONESHOT_CHEER_NO_SHEATHE);
|
||||
events.ScheduleEvent(EVENT_VICTORY_CHEER_3, 3s, 6500ms);
|
||||
break;
|
||||
case EVENT_VICTORY_CHEER_3:
|
||||
if (me->IsAlive())
|
||||
me->HandleEmoteCommand(EMOTE_ONESHOT_CHEER_NO_SHEATHE);
|
||||
events.ScheduleEvent(EVENT_VICTORY_DANCE, 3s, 6500ms);
|
||||
break;
|
||||
case EVENT_VICTORY_DANCE:
|
||||
if (me->IsAlive())
|
||||
me->HandleEmoteCommand(EMOTE_ONESHOT_DANCE);
|
||||
events.ScheduleEvent(EVENT_VICTORY_DESPAWN, 15s, 60s);
|
||||
break;
|
||||
case EVENT_VICTORY_DESPAWN:
|
||||
me->DespawnOrUnsummon();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void DoAction(int32 action) override
|
||||
{
|
||||
if (action == ACTION_VICTORY_EMOTE)
|
||||
{
|
||||
events.Reset();
|
||||
me->AttackStop();
|
||||
me->CombatStop(true);
|
||||
me->GetMotionMaster()->Clear();
|
||||
|
||||
events.ScheduleEvent(EVENT_VICTORY_CHEER_2, 3s, 6500ms);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -839,6 +919,37 @@ struct npc_ulduar_hodir_druid : public ScriptedAI
|
||||
}
|
||||
events.Repeat(3s);
|
||||
break;
|
||||
case EVENT_VICTORY_CHEER_2:
|
||||
if (me->IsAlive())
|
||||
me->HandleEmoteCommand(EMOTE_ONESHOT_CHEER_NO_SHEATHE);
|
||||
events.ScheduleEvent(EVENT_VICTORY_CHEER_3, 3s, 6500ms);
|
||||
break;
|
||||
case EVENT_VICTORY_CHEER_3:
|
||||
if (me->IsAlive())
|
||||
me->HandleEmoteCommand(EMOTE_ONESHOT_CHEER_NO_SHEATHE);
|
||||
events.ScheduleEvent(EVENT_VICTORY_DANCE, 3s, 6500ms);
|
||||
break;
|
||||
case EVENT_VICTORY_DANCE:
|
||||
if (me->IsAlive())
|
||||
me->HandleEmoteCommand(EMOTE_ONESHOT_DANCE);
|
||||
events.ScheduleEvent(EVENT_VICTORY_DESPAWN, 15s, 60s);
|
||||
break;
|
||||
case EVENT_VICTORY_DESPAWN:
|
||||
me->DespawnOrUnsummon();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void DoAction(int32 action) override
|
||||
{
|
||||
if (action == ACTION_VICTORY_EMOTE)
|
||||
{
|
||||
events.Reset();
|
||||
me->AttackStop();
|
||||
me->CombatStop(true);
|
||||
me->GetMotionMaster()->Clear();
|
||||
|
||||
events.ScheduleEvent(EVENT_VICTORY_CHEER_2, 3s, 6500ms);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -932,6 +1043,37 @@ struct npc_ulduar_hodir_shaman : public ScriptedAI
|
||||
events.Repeat(30s);
|
||||
break;
|
||||
}
|
||||
case EVENT_VICTORY_CHEER_2:
|
||||
if (me->IsAlive())
|
||||
me->HandleEmoteCommand(EMOTE_ONESHOT_CHEER_NO_SHEATHE);
|
||||
events.ScheduleEvent(EVENT_VICTORY_CHEER_3, 3s, 6500ms);
|
||||
break;
|
||||
case EVENT_VICTORY_CHEER_3:
|
||||
if (me->IsAlive())
|
||||
me->HandleEmoteCommand(EMOTE_ONESHOT_CHEER_NO_SHEATHE);
|
||||
events.ScheduleEvent(EVENT_VICTORY_DANCE, 3s, 6500ms);
|
||||
break;
|
||||
case EVENT_VICTORY_DANCE:
|
||||
if (me->IsAlive())
|
||||
me->HandleEmoteCommand(EMOTE_ONESHOT_DANCE);
|
||||
events.ScheduleEvent(EVENT_VICTORY_DESPAWN, 15s, 60s);
|
||||
break;
|
||||
case EVENT_VICTORY_DESPAWN:
|
||||
me->DespawnOrUnsummon();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void DoAction(int32 action) override
|
||||
{
|
||||
if (action == ACTION_VICTORY_EMOTE)
|
||||
{
|
||||
events.Reset();
|
||||
me->AttackStop();
|
||||
me->CombatStop(true);
|
||||
me->GetMotionMaster()->Clear();
|
||||
|
||||
events.ScheduleEvent(EVENT_VICTORY_CHEER_2, 3s, 6500ms);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1036,6 +1178,37 @@ struct npc_ulduar_hodir_mage : public ScriptedAI
|
||||
events.Repeat(5s);
|
||||
}
|
||||
break;
|
||||
case EVENT_VICTORY_CHEER_2:
|
||||
if (me->IsAlive())
|
||||
me->HandleEmoteCommand(EMOTE_ONESHOT_CHEER_NO_SHEATHE);
|
||||
events.ScheduleEvent(EVENT_VICTORY_CHEER_3, 3s, 6500ms);
|
||||
break;
|
||||
case EVENT_VICTORY_CHEER_3:
|
||||
if (me->IsAlive())
|
||||
me->HandleEmoteCommand(EMOTE_ONESHOT_CHEER_NO_SHEATHE);
|
||||
events.ScheduleEvent(EVENT_VICTORY_DANCE, 3s, 6500ms);
|
||||
break;
|
||||
case EVENT_VICTORY_DANCE:
|
||||
if (me->IsAlive())
|
||||
me->HandleEmoteCommand(EMOTE_ONESHOT_DANCE);
|
||||
events.ScheduleEvent(EVENT_VICTORY_DESPAWN, 15s, 60s);
|
||||
break;
|
||||
case EVENT_VICTORY_DESPAWN:
|
||||
me->DespawnOrUnsummon();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void DoAction(int32 action) override
|
||||
{
|
||||
if (action == ACTION_VICTORY_EMOTE)
|
||||
{
|
||||
events.Reset();
|
||||
me->AttackStop();
|
||||
me->CombatStop(true);
|
||||
me->GetMotionMaster()->Clear();
|
||||
|
||||
events.ScheduleEvent(EVENT_VICTORY_CHEER_2, 3s, 6500ms);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -22,6 +22,7 @@
|
||||
#include "GameTime.h"
|
||||
#include "GridNotifiers.h"
|
||||
#include "MapMgr.h"
|
||||
#include "ObjectAccessor.h"
|
||||
#include "PassiveAI.h"
|
||||
#include "Player.h"
|
||||
#include "ScriptedCreature.h"
|
||||
@@ -60,6 +61,7 @@ enum SpellData
|
||||
SPELL_ROCKET_STRIKE_BOTH = 65034, // VX-001 fires both mounted rockets
|
||||
SPELL_ROCKET_STRIKE_TARGET = 63681, // Cast by a fired rocket; picks the impact target (prefers ranged)
|
||||
SPELL_SUMMON_ROCKET_STRIKE = 63036, // Summons the ground strike at the chosen target
|
||||
SPELL_ROCKET_STRIKE_DAMAGE = 63041,
|
||||
NPC_ROCKET_VISUAL = 34050,
|
||||
NPC_ROCKET_STRIKE_N = 34047,
|
||||
|
||||
@@ -219,6 +221,9 @@ enum EVENTS
|
||||
EVENT_SUMMON_EMERGENCY_FIRE_BOTS = 68,
|
||||
EVENT_EMERGENCY_BOT_CHECK = 69,
|
||||
EVENT_EMERGENCY_BOT_ATTACK = 70,
|
||||
|
||||
// Rocket (Mimiron Visual):
|
||||
EVENT_ROCKET_FIRE = 71,
|
||||
};
|
||||
|
||||
enum Actions
|
||||
@@ -1359,7 +1364,7 @@ struct npc_ulduar_vx001 : public ScriptedAI
|
||||
_events.Repeat(10s);
|
||||
break;
|
||||
case EVENT_SPELL_ROCKET_STRIKE:
|
||||
me->CastSpell(me, _phase == 2 ? SPELL_ROCKET_STRIKE_SINGLE : SPELL_ROCKET_STRIKE_BOTH, true);
|
||||
me->CastSpell(me, _phase == 2 ? SPELL_ROCKET_STRIKE_SINGLE : SPELL_ROCKET_STRIKE_BOTH);
|
||||
_events.Repeat(20s);
|
||||
_events.ScheduleEvent(EVENT_REINSTALL_ROCKETS, 10s);
|
||||
break;
|
||||
@@ -1877,9 +1882,14 @@ class spell_mimiron_rocket_strike_target_select : public SpellScript
|
||||
|
||||
void HandleScript(SpellEffIndex /*effIndex*/)
|
||||
{
|
||||
ObjectGuid originalCaster = GetOriginalCaster() ? GetOriginalCaster()->GetGUID() : GetCaster()->GetGUID();
|
||||
GetCaster()->CastSpell(GetHitUnit(), SPELL_SUMMON_ROCKET_STRIKE, TRIGGERED_FULL_MASK, nullptr, nullptr, originalCaster);
|
||||
GetCaster()->SetDisplayId(11686); // hide the spent rocket until it is reloaded
|
||||
// Spawn the strike trigger now, so its warning visual and 5s fuse run while the missile is still to come.
|
||||
// The rocket fires the missile later, timed to land as the fuse expires (see npc_ulduar_mimiron_rocket).
|
||||
if (Creature* rocket = GetCaster()->ToCreature())
|
||||
if (Creature* trigger = rocket->SummonCreature(NPC_ROCKET_STRIKE_N, *GetHitUnit(), TEMPSUMMON_TIMED_DESPAWN, 6000))
|
||||
{
|
||||
rocket->AI()->SetGUID(trigger->GetGUID(), 0);
|
||||
rocket->AI()->SetGUID(GetHitUnit()->GetGUID(), 1);
|
||||
}
|
||||
}
|
||||
|
||||
void Register() override
|
||||
@@ -1906,19 +1916,70 @@ struct npc_ulduar_mimiron_rocket : public NullCreatureAI
|
||||
me->AddUnitState(UNIT_STATE_NO_ENVIRONMENT_UPD);
|
||||
}
|
||||
|
||||
void SetData(uint32 /*id*/, uint32 /*value*/) override
|
||||
void SetGUID(ObjectGuid const& guid, int32 id) override
|
||||
{
|
||||
me->GetMotionMaster()->MovePoint(0, me->GetPositionX(), me->GetPositionY(), me->GetPositionZ() + 100.0f, FORCED_MOVEMENT_NONE, 0.f, false, true);
|
||||
if (id == 0)
|
||||
{
|
||||
_strikeTrigger = guid;
|
||||
// Delay the shot so the 63036 missile (7 yd/s client-side) lands as the strike trigger's 5s fuse expires.
|
||||
_travelMs = 0;
|
||||
if (Creature* trigger = ObjectAccessor::GetCreature(*me, guid))
|
||||
_travelMs = uint32(me->GetExactDist(trigger) / 7.0f * 1000.0f);
|
||||
_events.RescheduleEvent(EVENT_ROCKET_FIRE, Milliseconds(_travelMs < 5000 ? 5000 - _travelMs : 0));
|
||||
}
|
||||
else
|
||||
_strikeVictim = guid;
|
||||
}
|
||||
|
||||
void UpdateAI(uint32 /*diff*/) override
|
||||
ObjectGuid GetGUID(int32 /*id*/) const override
|
||||
{
|
||||
if (!me->GetVehicle())
|
||||
return _strikeTrigger;
|
||||
}
|
||||
|
||||
void UpdateAI(uint32 diff) override
|
||||
{
|
||||
_events.Update(diff);
|
||||
if (_events.ExecuteEvent() == EVENT_ROCKET_FIRE)
|
||||
{
|
||||
me->SetSpeed(MOVE_RUN, me->GetSpeedRate(MOVE_RUN) + 0.4f, false);
|
||||
me->SetSpeed(MOVE_FLIGHT, me->GetSpeedRate(MOVE_RUN), false);
|
||||
if (Unit* victim = ObjectAccessor::GetUnit(*me, _strikeVictim))
|
||||
me->CastSpell(victim, SPELL_SUMMON_ROCKET_STRIKE, true);
|
||||
if (Creature* trigger = ObjectAccessor::GetCreature(*me, _strikeTrigger))
|
||||
trigger->AI()->SetData(0, _travelMs);
|
||||
me->SetDisplayId(11686); // hide the spent rocket until it is reloaded
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
EventMap _events;
|
||||
ObjectGuid _strikeTrigger;
|
||||
ObjectGuid _strikeVictim;
|
||||
uint32 _travelMs = 0;
|
||||
};
|
||||
|
||||
// 63036 - Summon Rocket Strike
|
||||
class spell_mimiron_summon_rocket_strike : public SpellScript
|
||||
{
|
||||
PrepareSpellScript(spell_mimiron_summon_rocket_strike);
|
||||
|
||||
void SetDest(SpellDestination& dest)
|
||||
{
|
||||
// Land on the pre-spawned strike trigger, not on the target's current position.
|
||||
if (Creature* rocket = GetCaster()->ToCreature())
|
||||
if (Creature* trigger = ObjectAccessor::GetCreature(*rocket, rocket->AI()->GetGUID()))
|
||||
dest.Relocate(*trigger);
|
||||
}
|
||||
|
||||
void PreventSummon(SpellEffIndex effIndex)
|
||||
{
|
||||
// The strike trigger is pre-spawned on target selection; this cast only provides the missile visual.
|
||||
PreventHitDefaultEffect(effIndex);
|
||||
}
|
||||
|
||||
void Register() override
|
||||
{
|
||||
OnDestinationTargetSelect += SpellDestinationTargetSelectFn(spell_mimiron_summon_rocket_strike::SetDest, EFFECT_0, TARGET_DEST_TARGET_ENEMY);
|
||||
OnEffectHit += SpellEffectFn(spell_mimiron_summon_rocket_strike::PreventSummon, EFFECT_0, SPELL_EFFECT_SUMMON);
|
||||
}
|
||||
};
|
||||
|
||||
struct npc_ulduar_bot_summon_trigger : public NullCreatureAI
|
||||
@@ -2338,11 +2399,24 @@ struct npc_ulduar_rocket_strike_trigger : public NullCreatureAI
|
||||
me->DespawnOrUnsummon(6s);
|
||||
}
|
||||
|
||||
void SetData(uint32 /*id*/, uint32 value) override
|
||||
{
|
||||
// Detonate in sync with the incoming missile; the 64064 tick is suppressed (spell_mimiron_rocket_strike_aura).
|
||||
_events.ScheduleEvent(1, Milliseconds(value));
|
||||
}
|
||||
|
||||
void UpdateAI(uint32 diff) override
|
||||
{
|
||||
_events.Update(diff);
|
||||
if (_events.ExecuteEvent() == 1)
|
||||
me->CastSpell(me, SPELL_ROCKET_STRIKE_DAMAGE, true);
|
||||
}
|
||||
|
||||
void SpellHitTarget(Unit* target, SpellInfo const* spell) override
|
||||
{
|
||||
if (!target || !spell)
|
||||
return;
|
||||
if (spell->Id == 63041)
|
||||
if (spell->Id == SPELL_ROCKET_STRIKE_DAMAGE)
|
||||
{
|
||||
if (target->GetEntry() == NPC_ASSAULT_BOT)
|
||||
me->CastSpell(me, 65040, true); // achievement Not-So-Friendly Fire
|
||||
@@ -2352,6 +2426,26 @@ struct npc_ulduar_rocket_strike_trigger : public NullCreatureAI
|
||||
c->AI()->SetData(0, 13);
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
EventMap _events;
|
||||
};
|
||||
|
||||
// 64064 - Rocket Strike
|
||||
class spell_mimiron_rocket_strike_aura : public AuraScript
|
||||
{
|
||||
PrepareAuraScript(spell_mimiron_rocket_strike_aura);
|
||||
|
||||
void HandlePeriodic(AuraEffect const* /*aurEff*/)
|
||||
{
|
||||
// No fuse tick: the strike trigger detonates in sync with the missile impact (npc_ulduar_rocket_strike_trigger).
|
||||
PreventDefaultAction();
|
||||
}
|
||||
|
||||
void Register() override
|
||||
{
|
||||
OnEffectPeriodic += AuraEffectPeriodicFn(spell_mimiron_rocket_strike_aura::HandlePeriodic, EFFECT_0, SPELL_AURA_PERIODIC_TRIGGER_SPELL);
|
||||
}
|
||||
};
|
||||
|
||||
class achievement_mimiron_firefighter : public AchievementCriteriaScript
|
||||
@@ -2415,6 +2509,8 @@ void AddSC_boss_mimiron()
|
||||
RegisterSpellScript(spell_ulduar_mimiron_mine_explosion);
|
||||
RegisterSpellScript(spell_mimiron_rocket_strike);
|
||||
RegisterSpellScript(spell_mimiron_rocket_strike_target_select);
|
||||
RegisterSpellScript(spell_mimiron_summon_rocket_strike);
|
||||
RegisterSpellScript(spell_mimiron_rocket_strike_aura);
|
||||
new go_ulduar_do_not_push_this_button();
|
||||
RegisterUlduarCreatureAI(npc_ulduar_flames_initial);
|
||||
RegisterUlduarCreatureAI(npc_ulduar_flames_spread);
|
||||
|
||||
@@ -174,6 +174,9 @@ enum YoggEvents
|
||||
EVENT_YS_DEAFENING_ROAR = 31,
|
||||
EVENT_YS_SUMMON_GUARDIAN = 32,
|
||||
EVENT_YS_SHADOW_BEACON = 33,
|
||||
|
||||
EVENT_SARA_WIPE_OPEN_DOOR = 40,
|
||||
EVENT_SARA_WIPE_RESPAWN = 41,
|
||||
};
|
||||
|
||||
enum NPCsGOs
|
||||
@@ -257,6 +260,7 @@ enum Misc
|
||||
EVENT_PHASE_ONE = 1,
|
||||
EVENT_PHASE_TWO = 2,
|
||||
EVENT_PHASE_THREE = 3,
|
||||
EVENT_PHASE_WIPE_RECOVERY = 4,
|
||||
|
||||
CRITERIA_NOT_GETTING_OLDER = 21001,
|
||||
|
||||
@@ -388,6 +392,7 @@ struct boss_yoggsaron_sara : public ScriptedAI
|
||||
float _summonSpeed;
|
||||
uint8 _currentIllusion;
|
||||
bool _isIllusionReversed;
|
||||
bool _isWipeRecovering = false;
|
||||
|
||||
void AttackStart(Unit*) override { }
|
||||
void MoveInLineOfSight(Unit*) override { }
|
||||
@@ -414,11 +419,10 @@ struct boss_yoggsaron_sara : public ScriptedAI
|
||||
if (!_EnterEvadeMode(why))
|
||||
return;
|
||||
|
||||
Position pos;
|
||||
pos = me->GetHomePosition();
|
||||
Position pos = me->GetHomePosition();
|
||||
me->NearTeleportTo(pos.GetPositionX(), pos.GetPositionY(), pos.GetPositionZ(), pos.GetOrientation());
|
||||
Reset();
|
||||
me->setActive(false);
|
||||
|
||||
HandleWipeRecovery();
|
||||
}
|
||||
|
||||
void EnableSara(bool apply)
|
||||
@@ -437,6 +441,18 @@ struct boss_yoggsaron_sara : public ScriptedAI
|
||||
}
|
||||
}
|
||||
|
||||
void HandleWipeRecovery()
|
||||
{
|
||||
_isWipeRecovering = true;
|
||||
Reset();
|
||||
me->SetVisible(false);
|
||||
|
||||
events.SetPhase(EVENT_PHASE_WIPE_RECOVERY);
|
||||
events.ScheduleEvent(EVENT_SARA_WIPE_OPEN_DOOR, 20s, 0, EVENT_PHASE_WIPE_RECOVERY);
|
||||
events.ScheduleEvent(EVENT_SARA_WIPE_RESPAWN, 30s, 0, EVENT_PHASE_WIPE_RECOVERY);
|
||||
me->setActive(true);
|
||||
}
|
||||
|
||||
void Reset() override
|
||||
{
|
||||
// Whisper only on a real phase 1 wipe, not on the initial reset
|
||||
@@ -455,17 +471,20 @@ struct boss_yoggsaron_sara : public ScriptedAI
|
||||
events.Reset();
|
||||
summons.DespawnAll();
|
||||
|
||||
me->SetVisible(true);
|
||||
if (!_isWipeRecovering)
|
||||
{
|
||||
me->SetVisible(true);
|
||||
SpawnClouds();
|
||||
UpdateKeeperSpawns();
|
||||
}
|
||||
|
||||
me->SetDisplayId(me->GetNativeDisplayId());
|
||||
me->SetDisableGravity(true);
|
||||
me->SetFaction(FACTION_FRIENDLY);
|
||||
me->ClearUnitState(UNIT_STATE_EVADE);
|
||||
EnableSara(false);
|
||||
SpawnClouds();
|
||||
|
||||
_initFight = 1;
|
||||
|
||||
UpdateKeeperSpawns();
|
||||
_summonedGuardiansCount = 0;
|
||||
_p2TalkTimer = 0;
|
||||
_secondPhase = false;
|
||||
@@ -479,9 +498,12 @@ struct boss_yoggsaron_sara : public ScriptedAI
|
||||
_instance->DoRemoveAurasDueToSpellOnPlayers(SPELL_SANITY);
|
||||
if (Creature* voice = _instance->GetCreature(DATA_VOICE_OF_YOGG_SARON))
|
||||
voice->AI()->DoAction(ACTION_VOICE_STOP);
|
||||
_instance->SetBossState(BOSS_YOGGSARON, NOT_STARTED);
|
||||
if (GameObject* go = _instance->GetGameObject(DATA_YOGG_SARON_DOORS))
|
||||
go->SetGoState(GO_STATE_ACTIVE);
|
||||
if (!_isWipeRecovering)
|
||||
{
|
||||
_instance->SetBossState(BOSS_YOGGSARON, NOT_STARTED);
|
||||
if (GameObject* go = _instance->GetGameObject(DATA_YOGG_SARON_DOORS))
|
||||
go->SetGoState(GO_STATE_ACTIVE);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -490,9 +512,6 @@ struct boss_yoggsaron_sara : public ScriptedAI
|
||||
if (!_instance)
|
||||
return;
|
||||
|
||||
if (_instance->GetBossState(BOSS_VEZAX) != DONE)
|
||||
return;
|
||||
|
||||
_instance->DoStartTimedAchievement(ACHIEVEMENT_TIMED_TYPE_EVENT, CRITERIA_NOT_GETTING_OLDER);
|
||||
_instance->SetBossState(BOSS_YOGGSARON, IN_PROGRESS);
|
||||
me->SetInCombatWithZone();
|
||||
@@ -724,7 +743,6 @@ struct boss_yoggsaron_sara : public ScriptedAI
|
||||
void DamageTaken(Unit* attacker, uint32& damage, DamageEffectType /*damagetype*/, SpellSchoolMask /*damageSchoolMask*/) override
|
||||
{
|
||||
// Guardians can be spawned by walking into Ominous Clouds even when InitFight
|
||||
// never ran (e.g. Vezax not defeated); their novas must not start phase 2 then.
|
||||
if (!_instance || _instance->GetBossState(BOSS_YOGGSARON) != IN_PROGRESS || !attacker || attacker->GetEntry() != NPC_GUARDIAN_OF_YS || _secondPhase)
|
||||
{
|
||||
damage = 0;
|
||||
@@ -769,6 +787,39 @@ struct boss_yoggsaron_sara : public ScriptedAI
|
||||
|
||||
void UpdateAI(uint32 diff) override
|
||||
{
|
||||
if (_isWipeRecovering)
|
||||
{
|
||||
events.Update(diff);
|
||||
while (uint32 eventId = events.ExecuteEvent())
|
||||
{
|
||||
switch (eventId)
|
||||
{
|
||||
case EVENT_SARA_WIPE_OPEN_DOOR:
|
||||
if (_instance)
|
||||
if (GameObject* go = _instance->GetGameObject(DATA_YOGG_SARON_DOORS))
|
||||
go->SetGoState(GO_STATE_ACTIVE);
|
||||
break;
|
||||
case EVENT_SARA_WIPE_RESPAWN:
|
||||
if (_instance)
|
||||
{
|
||||
if (GameObject* go = _instance->GetGameObject(DATA_YOGG_SARON_DOORS))
|
||||
go->SetGoState(GO_STATE_ACTIVE);
|
||||
|
||||
_instance->SetBossState(BOSS_YOGGSARON, NOT_STARTED);
|
||||
}
|
||||
|
||||
me->SetVisible(true);
|
||||
SpawnClouds();
|
||||
UpdateKeeperSpawns();
|
||||
events.Reset();
|
||||
_isWipeRecovering = false;
|
||||
me->setActive(false);
|
||||
break;
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (_initFight)
|
||||
{
|
||||
_initFight += diff;
|
||||
|
||||
@@ -219,6 +219,7 @@ public:
|
||||
// Shared
|
||||
EventMap _events;
|
||||
bool _mimironTramUsed;
|
||||
bool _algalonResummonPending;
|
||||
|
||||
void Initialize() override
|
||||
{
|
||||
@@ -234,6 +235,7 @@ public:
|
||||
// Shared
|
||||
_events.Reset();
|
||||
_mimironTramUsed = false;
|
||||
_algalonResummonPending = false;
|
||||
}
|
||||
|
||||
void FillInitialWorldStates(WorldPackets::WorldState::InitWorldStates& packet) override
|
||||
@@ -279,7 +281,7 @@ public:
|
||||
|
||||
uint32 algalonTimer =
|
||||
GetPersistentData(PERSISTENT_DATA_ALGALON_TIMER);
|
||||
if (!GetObjectGuid(BOSS_ALGALON) && algalonTimer
|
||||
if (!GetObjectGuid(BOSS_ALGALON) && !_algalonResummonPending && algalonTimer
|
||||
&& (algalonTimer <= 60
|
||||
|| algalonTimer == TIMER_ALGALON_TO_SUMMON))
|
||||
{
|
||||
@@ -708,6 +710,10 @@ public:
|
||||
StorePersistentData(PERSISTENT_DATA_ALGALON_TIMER, 60);
|
||||
_events.RescheduleEvent(EVENT_UPDATE_ALGALON_TIMER, 1min);
|
||||
return;
|
||||
case DATA_RESUMMON_ALGALON:
|
||||
_algalonResummonPending = true;
|
||||
_events.RescheduleEvent(EVENT_RESUMMON_ALGALON, 2s);
|
||||
return;
|
||||
case DATA_ALGALON_SUMMON_STATE:
|
||||
case DATA_ALGALON_DEFEATED:
|
||||
DoUpdateWorldState(WORLD_STATE_ULDUAR_ALGALON_TIMER_ENABLED, 0);
|
||||
@@ -931,7 +937,14 @@ public:
|
||||
SetData(DATA_ALGALON_DEFEATED, 1);
|
||||
if (Creature* algalon = GetCreature(BOSS_ALGALON))
|
||||
algalon->AI()->DoAction(ACTION_DESPAWN_ALGALON);
|
||||
break;
|
||||
}
|
||||
case EVENT_RESUMMON_ALGALON:
|
||||
_algalonResummonPending = false;
|
||||
if (!GetCreature(BOSS_ALGALON))
|
||||
if (Creature* algalon = instance->SummonCreature(NPC_ALGALON, AlgalonSummonPos))
|
||||
algalon->AI()->DoAction(ACTION_START_INTRO);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -984,6 +997,24 @@ public:
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool CheckRequiredBosses(uint32 bossId, Player const* player) const override
|
||||
{
|
||||
if (_SkipCheckRequiredBosses(player))
|
||||
return true;
|
||||
|
||||
switch (bossId)
|
||||
{
|
||||
case BOSS_YOGGSARON:
|
||||
if (GetBossState(BOSS_VEZAX) != DONE)
|
||||
return false;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
|
||||
@@ -111,6 +111,7 @@ enum UlduarData
|
||||
DATA_UNIVERSE_GLOBE = 608,
|
||||
DATA_ALGALON_TRAPDOOR = 609,
|
||||
DATA_BRANN_BRONZEBEARD_ALG = 610,
|
||||
DATA_RESUMMON_ALGALON = 611,
|
||||
|
||||
// Achievements
|
||||
DATA_DWARFAGEDDON = 700,
|
||||
@@ -362,9 +363,11 @@ enum UlduarMisc
|
||||
ACTION_TOWER_OF_LIFE_DESTROYED = 4,
|
||||
|
||||
EVENT_UPDATE_ALGALON_TIMER = 1,
|
||||
EVENT_RESUMMON_ALGALON = 2,
|
||||
ACTION_FEEDS_ON_TEARS_FAILED = 0,
|
||||
ACTION_INIT_ALGALON = 1,
|
||||
ACTION_DESPAWN_ALGALON = 2,
|
||||
ACTION_START_INTRO = 3,
|
||||
|
||||
TIMER_ALGALON_DEFEATED = 300,
|
||||
TIMER_ALGALON_TO_SUMMON = 200,
|
||||
|
||||
@@ -630,7 +630,7 @@ public:
|
||||
if (TempSummon* summon = me->ToTempSummon())
|
||||
if (Unit* owner = summon->GetSummonerUnit())
|
||||
if (Player* player = owner->ToPlayer())
|
||||
player->KilledMonsterCredit(me->GetEntry());
|
||||
player->RewardPlayerAndGroupAtEvent(me->GetEntry(), player);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
@@ -15,13 +15,19 @@
|
||||
* with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include "Containers.h"
|
||||
#include "CreatureScript.h"
|
||||
#include "GameObjectAI.h"
|
||||
#include "GameObjectScript.h"
|
||||
#include "Map.h"
|
||||
#include "MotionMaster.h"
|
||||
#include "PassiveAI.h"
|
||||
#include "Player.h"
|
||||
#include "ScriptedCreature.h"
|
||||
#include "ScriptedEscortAI.h"
|
||||
#include "SpellInfo.h"
|
||||
#include "SpellScript.h"
|
||||
#include "WaypointMgr.h"
|
||||
|
||||
class npc_attracted_reef_bull : public CreatureScript
|
||||
{
|
||||
@@ -65,123 +71,6 @@ public:
|
||||
}
|
||||
};
|
||||
|
||||
/*######
|
||||
## npc_apothecary_hanes
|
||||
######*/
|
||||
enum Entries
|
||||
{
|
||||
NPC_APOTHECARY_HANES = 23784,
|
||||
NPC_HANES_FIRE_TRIGGER = 23968,
|
||||
QUEST_TRAIL_OF_FIRE = 11241,
|
||||
SPELL_COSMETIC_LOW_POLY_FIRE = 56274,
|
||||
SPELL_HEALING_POTION = 17534
|
||||
};
|
||||
|
||||
class npc_apothecary_hanes : public CreatureScript
|
||||
{
|
||||
public:
|
||||
npc_apothecary_hanes() : CreatureScript("npc_apothecary_hanes") { }
|
||||
|
||||
bool OnQuestAccept(Player* player, Creature* creature, Quest const* quest) override
|
||||
{
|
||||
if (quest->GetQuestId() == QUEST_TRAIL_OF_FIRE)
|
||||
{
|
||||
creature->SetFaction(player->GetTeamId() == TEAM_ALLIANCE ? FACTION_ESCORTEE_A_PASSIVE : FACTION_ESCORTEE_H_PASSIVE);
|
||||
creature->SetWalk(true);
|
||||
CAST_AI(npc_escortAI, (creature->AI()))->Start(true, player->GetGUID());
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
struct npc_Apothecary_HanesAI : public npc_escortAI
|
||||
{
|
||||
npc_Apothecary_HanesAI(Creature* creature) : npc_escortAI(creature) { }
|
||||
uint32 PotTimer;
|
||||
|
||||
void Reset() override
|
||||
{
|
||||
SetDespawnAtFar(false);
|
||||
PotTimer = 10000; //10 sec cooldown on potion
|
||||
}
|
||||
|
||||
void JustDied(Unit* /*killer*/) override
|
||||
{
|
||||
if (Player* player = GetPlayerForEscort())
|
||||
player->FailQuest(QUEST_TRAIL_OF_FIRE);
|
||||
}
|
||||
|
||||
void UpdateEscortAI(uint32 diff) override
|
||||
{
|
||||
if (HealthBelowPct(75))
|
||||
{
|
||||
if (PotTimer <= diff)
|
||||
{
|
||||
DoCast(me, SPELL_HEALING_POTION, true);
|
||||
PotTimer = 10000;
|
||||
}
|
||||
else PotTimer -= diff;
|
||||
}
|
||||
if (GetAttack() && UpdateVictim())
|
||||
DoMeleeAttackIfReady();
|
||||
}
|
||||
|
||||
using CreatureAI::WaypointReached;
|
||||
void WaypointReached(uint32 waypointId) override
|
||||
{
|
||||
Player* player = GetPlayerForEscort();
|
||||
if (!player)
|
||||
return;
|
||||
|
||||
switch (waypointId)
|
||||
{
|
||||
case 1:
|
||||
me->SetReactState(REACT_AGGRESSIVE);
|
||||
me->SetWalk(false);
|
||||
break;
|
||||
case 23:
|
||||
player->GroupEventHappens(QUEST_TRAIL_OF_FIRE, me);
|
||||
me->DespawnOrUnsummon();
|
||||
break;
|
||||
case 5:
|
||||
if (Unit* Trigger = me->FindNearestCreature(NPC_HANES_FIRE_TRIGGER, 10.0f))
|
||||
Trigger->CastSpell(Trigger, SPELL_COSMETIC_LOW_POLY_FIRE, false);
|
||||
me->SetWalk(true);
|
||||
break;
|
||||
case 6:
|
||||
if (Unit* Trigger = me->FindNearestCreature(NPC_HANES_FIRE_TRIGGER, 10.0f))
|
||||
Trigger->CastSpell(Trigger, SPELL_COSMETIC_LOW_POLY_FIRE, false);
|
||||
me->SetWalk(false);
|
||||
break;
|
||||
case 8:
|
||||
if (Unit* Trigger = me->FindNearestCreature(NPC_HANES_FIRE_TRIGGER, 10.0f))
|
||||
Trigger->CastSpell(Trigger, SPELL_COSMETIC_LOW_POLY_FIRE, false);
|
||||
me->SetWalk(true);
|
||||
break;
|
||||
case 9:
|
||||
if (Unit* Trigger = me->FindNearestCreature(NPC_HANES_FIRE_TRIGGER, 10.0f))
|
||||
Trigger->CastSpell(Trigger, SPELL_COSMETIC_LOW_POLY_FIRE, false);
|
||||
break;
|
||||
case 10:
|
||||
me->SetWalk(false);
|
||||
break;
|
||||
case 13:
|
||||
me->SetWalk(true);
|
||||
break;
|
||||
case 14:
|
||||
if (Unit* Trigger = me->FindNearestCreature(NPC_HANES_FIRE_TRIGGER, 10.0f))
|
||||
Trigger->CastSpell(Trigger, SPELL_COSMETIC_LOW_POLY_FIRE, false);
|
||||
me->SetWalk(false);
|
||||
break;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
CreatureAI* GetAI(Creature* creature) const override
|
||||
{
|
||||
return new npc_Apothecary_HanesAI(creature);
|
||||
}
|
||||
};
|
||||
|
||||
/*######
|
||||
## npc_plaguehound_tracker
|
||||
######*/
|
||||
@@ -417,10 +306,266 @@ class spell_the_cleansing_on_death_cast_on_master : public SpellScript
|
||||
}
|
||||
};
|
||||
|
||||
/*######
|
||||
## Quest 11529: Sorlof's Booty
|
||||
######*/
|
||||
|
||||
enum SorlofsBooty
|
||||
{
|
||||
NPC_SORLOF = 24914,
|
||||
NPC_THE_BIG_GUN = 24992,
|
||||
|
||||
SPELL_CANNON_ASSAULT = 45008,
|
||||
SPELL_SORLOFS_BOOTY = 45070,
|
||||
|
||||
// SPELL_BOULDER_ASSAULT_AURA = 44964, // Serverside, triggers 44965 every 3s
|
||||
SPELL_BOULDER_ASSAULT_HIT = 44966,
|
||||
SPELL_BOULDER_ASSAULT_FIRE = 44967,
|
||||
|
||||
// About the maximum range for broadside
|
||||
CANNON_RANGE = 200,
|
||||
|
||||
// The ship needs to ping Sorlof from wherever she is on her lap
|
||||
SORLOF_SEARCH_RANGE = 1000,
|
||||
|
||||
SORLOF_WANDER_DISTANCE = 10,
|
||||
|
||||
DATA_SORLOF_TAKE_PATH = 1,
|
||||
POINT_SORLOF_PATH = 1000,
|
||||
|
||||
// He only knits himself back together once he has broken off and headed home
|
||||
PATH_SORLOF_RETURN = 1032785,
|
||||
|
||||
// The gun's own SmartAI announces the booty on this data set.
|
||||
DATA_SORLOF_SLAIN = 1
|
||||
};
|
||||
|
||||
// 44965 - Boulder Assault
|
||||
class spell_sorlofs_booty_boulder_assault : public SpellScript
|
||||
{
|
||||
PrepareSpellScript(spell_sorlofs_booty_boulder_assault);
|
||||
|
||||
bool Validate(SpellInfo const* /*spellInfo*/) override
|
||||
{
|
||||
return ValidateSpellInfo({ SPELL_BOULDER_ASSAULT_HIT });
|
||||
}
|
||||
|
||||
void FilterTargets(std::list<WorldObject*>& targets)
|
||||
{
|
||||
if (targets.empty())
|
||||
return;
|
||||
|
||||
// One boulder per throw from implicit targets (no limit in DBC)
|
||||
WorldObject* target = Acore::Containers::SelectRandomContainerElement(targets);
|
||||
targets.clear();
|
||||
targets.push_back(target);
|
||||
}
|
||||
|
||||
void HandleScript(SpellEffIndex /*effIndex*/)
|
||||
{
|
||||
if (Unit* caster = GetCaster())
|
||||
caster->CastSpell(GetHitUnit(), SPELL_BOULDER_ASSAULT_HIT, true);
|
||||
}
|
||||
|
||||
void Register() override
|
||||
{
|
||||
OnObjectAreaTargetSelect += SpellObjectAreaTargetSelectFn(spell_sorlofs_booty_boulder_assault::FilterTargets, EFFECT_0, TARGET_UNIT_SRC_AREA_ENTRY);
|
||||
OnEffectHitTarget += SpellEffectFn(spell_sorlofs_booty_boulder_assault::HandleScript, EFFECT_0, SPELL_EFFECT_SCRIPT_EFFECT);
|
||||
}
|
||||
};
|
||||
|
||||
// 44966 - Boulder Assault
|
||||
class spell_sorlofs_booty_boulder_assault_hit : public SpellScript
|
||||
{
|
||||
PrepareSpellScript(spell_sorlofs_booty_boulder_assault_hit);
|
||||
|
||||
bool Validate(SpellInfo const* /*spellInfo*/) override
|
||||
{
|
||||
return ValidateSpellInfo({ SPELL_BOULDER_ASSAULT_FIRE });
|
||||
}
|
||||
|
||||
void HandleScript(SpellEffIndex /*effIndex*/)
|
||||
{
|
||||
if (Unit* target = GetHitUnit())
|
||||
target->CastSpell(target, SPELL_BOULDER_ASSAULT_FIRE, true);
|
||||
}
|
||||
|
||||
void Register() override
|
||||
{
|
||||
OnEffectHitTarget += SpellEffectFn(spell_sorlofs_booty_boulder_assault_hit::HandleScript, EFFECT_1, SPELL_EFFECT_SCRIPT_EFFECT);
|
||||
}
|
||||
};
|
||||
|
||||
uint32 GetSorlofPathForShipEvent(uint32 eventId)
|
||||
{
|
||||
switch (eventId)
|
||||
{
|
||||
// Departure events of the Sister Mercy's stops (TaxiPathNode.dbc path 778)
|
||||
case 16501: return 1032780;
|
||||
case 16502: return 1032781;
|
||||
case 16503: return 1032782;
|
||||
case 16504: return 1032783;
|
||||
case 16510: return 1032784;
|
||||
case 16511: return 1032785; // Return to spawn
|
||||
default: return 0;
|
||||
}
|
||||
}
|
||||
|
||||
struct npc_sorlof : public ScriptedAI
|
||||
{
|
||||
npc_sorlof(Creature* creature) : ScriptedAI(creature) { }
|
||||
|
||||
void Reset() override
|
||||
{
|
||||
_pathId = 0;
|
||||
_pathNode = 0;
|
||||
_advancePath = false;
|
||||
me->SetRegeneratingHealth(false);
|
||||
}
|
||||
|
||||
void SetData(uint32 id, uint32 value) override
|
||||
{
|
||||
if (id != DATA_SORLOF_TAKE_PATH)
|
||||
return;
|
||||
|
||||
_pathId = value;
|
||||
_pathNode = 0;
|
||||
_advancePath = true;
|
||||
me->SetRegeneratingHealth(value == PATH_SORLOF_RETURN);
|
||||
}
|
||||
|
||||
void MovementInform(uint32 type, uint32 id) override
|
||||
{
|
||||
if (type != POINT_MOTION_TYPE || id != POINT_SORLOF_PATH + _pathNode)
|
||||
return;
|
||||
|
||||
++_pathNode;
|
||||
_advancePath = true;
|
||||
}
|
||||
|
||||
void JustDied(Unit* /*killer*/) override
|
||||
{
|
||||
DoCastSelf(SPELL_SORLOFS_BOOTY, true);
|
||||
|
||||
if (Creature* gun = me->FindNearestCreature(NPC_THE_BIG_GUN, CANNON_RANGE))
|
||||
gun->AI()->SetData(DATA_SORLOF_SLAIN, DATA_SORLOF_SLAIN);
|
||||
}
|
||||
|
||||
void UpdateAI(uint32 /*diff*/) override
|
||||
{
|
||||
// Deferred out of MovementInform: that fires from the generator's DoFinalize, and
|
||||
// MotionMaster::DirectExpire then Resets the new top generator, whose DoReset stops
|
||||
// the spline it just launched. UpdateAI runs after the MotionMaster, so it sticks.
|
||||
if (_advancePath)
|
||||
{
|
||||
_advancePath = false;
|
||||
MoveToNextNode();
|
||||
}
|
||||
|
||||
if (UpdateVictim())
|
||||
DoMeleeAttackIfReady();
|
||||
}
|
||||
|
||||
private:
|
||||
void MoveToNextNode()
|
||||
{
|
||||
WaypointPath const* path = sWaypointMgr->GetPath(_pathId);
|
||||
if (!path)
|
||||
return;
|
||||
|
||||
// Each leg ends with him milling about where the ship has drawn up
|
||||
if (_pathNode >= path->Nodes.size())
|
||||
{
|
||||
me->GetMotionMaster()->MoveRandom(SORLOF_WANDER_DISTANCE);
|
||||
return;
|
||||
}
|
||||
|
||||
WaypointNode const& node = path->Nodes[_pathNode];
|
||||
me->SetWalk(node.MoveType == WAYPOINT_MOVE_TYPE_WALK);
|
||||
me->GetMotionMaster()->MovePoint(POINT_SORLOF_PATH + _pathNode, node.X, node.Y, node.Z);
|
||||
}
|
||||
|
||||
uint32 _pathId{ 0 };
|
||||
uint32 _pathNode{ 0 };
|
||||
bool _advancePath{ false };
|
||||
};
|
||||
|
||||
struct go_sister_mercy : public GameObjectAI
|
||||
{
|
||||
go_sister_mercy(GameObject* go) : GameObjectAI(go) { }
|
||||
|
||||
// Notify Sorlof the ship is departing towards the next broadside point
|
||||
void EventInform(uint32 eventId) override
|
||||
{
|
||||
uint32 pathId = GetSorlofPathForShipEvent(eventId);
|
||||
if (!pathId)
|
||||
return;
|
||||
|
||||
if (Creature* sorlof = me->FindNearestCreature(NPC_SORLOF, SORLOF_SEARCH_RANGE))
|
||||
sorlof->AI()->SetData(DATA_SORLOF_TAKE_PATH, pathId);
|
||||
}
|
||||
};
|
||||
|
||||
// 45045 - Big Cannon Assault Primer
|
||||
class spell_sorlofs_booty_cannon_primer : public SpellScript
|
||||
{
|
||||
PrepareSpellScript(spell_sorlofs_booty_cannon_primer);
|
||||
|
||||
bool Validate(SpellInfo const* spellInfo) override
|
||||
{
|
||||
return ValidateSpellInfo({ uint32(spellInfo->GetEffect(EFFECT_0).CalcValue()) });
|
||||
}
|
||||
|
||||
void HandleScript(SpellEffIndex /*effIndex*/)
|
||||
{
|
||||
// Primes the gun: the clicker performs the actual, timed Big Gun Assault cast.
|
||||
if (Unit* caster = GetCaster())
|
||||
caster->CastSpell(caster, GetSpellInfo()->Effects[EFFECT_0].CalcValue(), false);
|
||||
}
|
||||
|
||||
void Register() override
|
||||
{
|
||||
OnEffectHitTarget += SpellEffectFn(spell_sorlofs_booty_cannon_primer::HandleScript, EFFECT_0, SPELL_EFFECT_SCRIPT_EFFECT);
|
||||
}
|
||||
};
|
||||
|
||||
// 45013 - Big Gun Assault
|
||||
class spell_sorlofs_booty_big_gun_assault : public SpellScript
|
||||
{
|
||||
PrepareSpellScript(spell_sorlofs_booty_big_gun_assault);
|
||||
|
||||
bool Validate(SpellInfo const* /*spellInfo*/) override
|
||||
{
|
||||
return ValidateSpellInfo({ SPELL_CANNON_ASSAULT });
|
||||
}
|
||||
|
||||
void HandleDummy(SpellEffIndex /*effIndex*/)
|
||||
{
|
||||
// Implicit target of the dummy effect, resolved to The Big Gun by conditions
|
||||
Creature* gun = GetHitCreature();
|
||||
if (!gun)
|
||||
return;
|
||||
|
||||
Creature* sorlof = gun->FindNearestCreature(NPC_SORLOF, CANNON_RANGE);
|
||||
if (!sorlof)
|
||||
return;
|
||||
|
||||
if (!gun->IsWithinLOSInMap(sorlof, VMAP::ModelIgnoreFlags::M2, LINEOFSIGHT_CHECK_VMAP))
|
||||
return;
|
||||
|
||||
// Fired by the gun rather than by the player, this is correct
|
||||
gun->CastSpell(sorlof, SPELL_CANNON_ASSAULT, true);
|
||||
}
|
||||
|
||||
void Register() override
|
||||
{
|
||||
OnEffectHitTarget += SpellEffectFn(spell_sorlofs_booty_big_gun_assault::HandleDummy, EFFECT_0, SPELL_EFFECT_DUMMY);
|
||||
}
|
||||
};
|
||||
|
||||
void AddSC_howling_fjord()
|
||||
{
|
||||
new npc_attracted_reef_bull();
|
||||
new npc_apothecary_hanes();
|
||||
new npc_plaguehound_tracker();
|
||||
RegisterCreatureAI(npc_rodin_lightning_enabler);
|
||||
RegisterSpellScript(spell_hawk_hunting);
|
||||
@@ -428,4 +573,10 @@ void AddSC_howling_fjord()
|
||||
RegisterSpellScript(spell_the_cleansing_cleansing_soul);
|
||||
RegisterSpellScript(spell_the_cleansing_mirror_image_script_effect);
|
||||
RegisterSpellScript(spell_the_cleansing_on_death_cast_on_master);
|
||||
RegisterSpellScript(spell_sorlofs_booty_cannon_primer);
|
||||
RegisterSpellScript(spell_sorlofs_booty_big_gun_assault);
|
||||
RegisterSpellScript(spell_sorlofs_booty_boulder_assault);
|
||||
RegisterSpellScript(spell_sorlofs_booty_boulder_assault_hit);
|
||||
RegisterCreatureAI(npc_sorlof);
|
||||
RegisterGameObjectAI(go_sister_mercy);
|
||||
}
|
||||
|
||||
@@ -1357,6 +1357,27 @@ class spell_riding_jokkum : public AuraScript
|
||||
}
|
||||
};
|
||||
|
||||
// Quest Where Time Went Wrong (13048)
|
||||
class spell_q13048_time_period : public SpellScript
|
||||
{
|
||||
PrepareSpellScript(spell_q13048_time_period);
|
||||
|
||||
void HandleScriptEffect(SpellEffIndex /*effIndex*/)
|
||||
{
|
||||
Player* player = GetHitPlayer();
|
||||
|
||||
if (!player)
|
||||
return;
|
||||
|
||||
player->Unit::Say(GetSpellInfo()->Effects[EFFECT_0].CalcValue());
|
||||
}
|
||||
|
||||
void Register() override
|
||||
{
|
||||
OnEffectHitTarget += SpellEffectFn(spell_q13048_time_period::HandleScriptEffect, EFFECT_0, SPELL_EFFECT_SCRIPT_EFFECT);
|
||||
}
|
||||
};
|
||||
|
||||
void AddSC_storm_peaks()
|
||||
{
|
||||
RegisterCreatureAI(npc_frosthound);
|
||||
@@ -1385,4 +1406,5 @@ void AddSC_storm_peaks()
|
||||
RegisterSpellScript(spell_eject_passenger_wild_wyrm);
|
||||
RegisterSpellScript(spell_q13010_jokkum_summon);
|
||||
RegisterSpellScript(spell_riding_jokkum);
|
||||
RegisterSpellScript(spell_q13048_time_period);
|
||||
}
|
||||
|
||||
@@ -16,6 +16,7 @@
|
||||
*/
|
||||
|
||||
#include "CreatureScript.h"
|
||||
#include "ObjectAccessor.h"
|
||||
#include "ScriptedCreature.h"
|
||||
#include "SpellScriptLoader.h"
|
||||
#include "black_temple.h"
|
||||
@@ -98,8 +99,8 @@ struct boss_shade_of_akama : public BossAI
|
||||
{
|
||||
boss_shade_of_akama(Creature* creature) : BossAI(creature, DATA_SHADE_OF_AKAMA) { }
|
||||
|
||||
std::list<Creature*> channelers;
|
||||
std::list<Creature*> generators;
|
||||
GuidVector channelers;
|
||||
GuidVector generators;
|
||||
|
||||
void Reset() override
|
||||
{
|
||||
@@ -114,11 +115,13 @@ struct boss_shade_of_akama : public BossAI
|
||||
|
||||
void EnterEvadeMode(EvadeReason why) override
|
||||
{
|
||||
for (Creature* generator : generators)
|
||||
generator->AI()->DoAction(ACTION_GENERATOR_DESPAWN_ALL);
|
||||
for (ObjectGuid const& generatorGuid : generators)
|
||||
if (Creature* generator = ObjectAccessor::GetCreature(*me, generatorGuid))
|
||||
generator->AI()->DoAction(ACTION_GENERATOR_DESPAWN_ALL);
|
||||
|
||||
for (Creature* channeler : channelers)
|
||||
channeler->SetUnitFlag(UNIT_FLAG_NOT_SELECTABLE);
|
||||
for (ObjectGuid const& channelerGuid : channelers)
|
||||
if (Creature* channeler = ObjectAccessor::GetCreature(*me, channelerGuid))
|
||||
channeler->SetUnitFlag(UNIT_FLAG_NOT_SELECTABLE);
|
||||
|
||||
BossAI::EnterEvadeMode(why);
|
||||
}
|
||||
@@ -128,8 +131,9 @@ struct boss_shade_of_akama : public BossAI
|
||||
BossAI::JustDied(killer);
|
||||
me->CastSpell(me, SPELL_SHADE_OF_AKAMA_TRIGGER, true);
|
||||
|
||||
for (Creature* generator : generators)
|
||||
generator->AI()->DoAction(ACTION_GENERATOR_DESPAWN_ALL);
|
||||
for (ObjectGuid const& generatorGuid : generators)
|
||||
if (Creature* generator = ObjectAccessor::GetCreature(*me, generatorGuid))
|
||||
generator->AI()->DoAction(ACTION_GENERATOR_DESPAWN_ALL);
|
||||
|
||||
if (Creature* akama = instance->GetCreature(DATA_AKAMA_SHADE))
|
||||
akama->AI()->DoAction(ACTION_AKAMA_START_OUTRO);
|
||||
@@ -141,14 +145,22 @@ struct boss_shade_of_akama : public BossAI
|
||||
{
|
||||
instance->SetBossState(DATA_SHADE_OF_AKAMA, IN_PROGRESS);
|
||||
|
||||
me->GetCreatureListWithEntryInGrid(channelers, NPC_ASHTONGUE_CHANNELER, 40.0f);
|
||||
me->GetCreatureListWithEntryInGrid(generators, NPC_CREATURE_GENERATOR_AKAMA, 100.0f);
|
||||
std::list<Creature*> channelerList;
|
||||
std::list<Creature*> generatorList;
|
||||
me->GetCreatureListWithEntryInGrid(channelerList, NPC_ASHTONGUE_CHANNELER, 40.0f);
|
||||
me->GetCreatureListWithEntryInGrid(generatorList, NPC_CREATURE_GENERATOR_AKAMA, 100.0f);
|
||||
|
||||
for (Creature* channeler : channelers)
|
||||
for (Creature* channeler : channelerList)
|
||||
{
|
||||
channelers.push_back(channeler->GetGUID());
|
||||
channeler->RemoveUnitFlag(UNIT_FLAG_NOT_SELECTABLE);
|
||||
}
|
||||
|
||||
for (Creature* generator : generators)
|
||||
for (Creature* generator : generatorList)
|
||||
{
|
||||
generators.push_back(generator->GetGUID());
|
||||
generator->AI()->DoAction(ACTION_GENERATOR_START);
|
||||
}
|
||||
|
||||
ScheduleTimedEvent(1200ms, [&]
|
||||
{
|
||||
@@ -169,8 +181,9 @@ struct boss_shade_of_akama : public BossAI
|
||||
me->RemoveAurasDueToSpell(SPELL_AKAMA_SOUL_CHANNEL);
|
||||
scheduler.CancelAll();
|
||||
|
||||
for (Creature* generator : generators)
|
||||
generator->AI()->DoAction(ACTION_GENERATOR_STOP);
|
||||
for (ObjectGuid const& generatorGuid : generators)
|
||||
if (Creature* generator = ObjectAccessor::GetCreature(*me, generatorGuid))
|
||||
generator->AI()->DoAction(ACTION_GENERATOR_STOP);
|
||||
|
||||
if (Creature* akama = instance->GetCreature(DATA_AKAMA_SHADE))
|
||||
{
|
||||
@@ -222,7 +235,6 @@ struct npc_akama_shade : public ScriptedAI
|
||||
_sayLowHealth = false;
|
||||
_died = false;
|
||||
scheduler.CancelAll();
|
||||
_generators.clear();
|
||||
}
|
||||
|
||||
void MovementInform(uint32 type, uint32 point) override
|
||||
@@ -280,8 +292,9 @@ struct npc_akama_shade : public ScriptedAI
|
||||
else if (damage >= me->GetHealth() && !_died)
|
||||
{
|
||||
_died = true;
|
||||
me->GetCreatureListWithEntryInGrid(_generators, NPC_CREATURE_GENERATOR_AKAMA, 100.0f);
|
||||
for (Creature* generator : _generators)
|
||||
std::list<Creature*> generators;
|
||||
me->GetCreatureListWithEntryInGrid(generators, NPC_CREATURE_GENERATOR_AKAMA, 100.0f);
|
||||
for (Creature* generator : generators)
|
||||
generator->AI()->DoAction(ACTION_GENERATOR_DESPAWN_ALL);
|
||||
|
||||
damage = me->GetHealth() - 1;
|
||||
@@ -347,7 +360,6 @@ struct npc_akama_shade : public ScriptedAI
|
||||
private:
|
||||
bool _sayLowHealth;
|
||||
bool _died;
|
||||
std::list<Creature *> _generators;
|
||||
};
|
||||
|
||||
struct npc_creature_generator_akama : public ScriptedAI
|
||||
|
||||
@@ -186,27 +186,24 @@ struct npc_supremus_punch_invisible_stalker : public ScriptedAI
|
||||
{
|
||||
npc_supremus_punch_invisible_stalker(Creature* creature) : ScriptedAI(creature) { }
|
||||
|
||||
void IsSummonedBy(WorldObject* /*summoner*/) override
|
||||
void IsSummonedBy(WorldObject* summoner) override
|
||||
{
|
||||
me->SetInCombatWithZone();
|
||||
if (Unit* target = SelectTarget(SelectTargetMethod::Random, 0, 100.0f, true))
|
||||
me->AddThreat(target, 10000.f);
|
||||
|
||||
DoCastSelf(SPELL_MOLTEN_FLAME, true);
|
||||
|
||||
// Trigger creatures cannot have their own threat list, pick the chase target from the summoner's
|
||||
if (Creature* supremus = summoner->ToCreature())
|
||||
if (Unit* target = supremus->AI()->SelectTarget(SelectTargetMethod::Random, 0, 100.0f, true))
|
||||
me->GetMotionMaster()->MoveFollow(target, 0.0f, 0.0f);
|
||||
|
||||
scheduler.Schedule(6s, 10s, [this](TaskContext /*context*/)
|
||||
{
|
||||
me->CombatStop();
|
||||
me->SetReactState(REACT_PASSIVE);
|
||||
me->GetMotionMaster()->MoveIdle();
|
||||
});
|
||||
}
|
||||
|
||||
void UpdateAI(uint32 diff) override
|
||||
{
|
||||
scheduler.Update(diff);
|
||||
|
||||
if (!UpdateVictim())
|
||||
return;
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -71,7 +71,13 @@ enum PaladinSpells
|
||||
|
||||
SPELL_PALADIN_SEAL_OF_RIGHTEOUSNESS = 25742,
|
||||
|
||||
SPELL_PALADIN_DEVOTION_AURA_R1 = 465,
|
||||
SPELL_PALADIN_RETRIBUTION_AURA_R1 = 7294,
|
||||
SPELL_PALADIN_CONCENTRACTION_AURA = 19746,
|
||||
SPELL_PALADIN_SHADOW_RESISTANCE_AURA_R1 = 19876,
|
||||
SPELL_PALADIN_FROST_RESISTANCE_AURA_R1 = 19888,
|
||||
SPELL_PALADIN_FIRE_RESISTANCE_AURA_R1 = 19891,
|
||||
SPELL_PALADIN_CRUSADER_AURA = 32223,
|
||||
SPELL_PALADIN_SANCTIFIED_RETRIBUTION_R1 = 31869,
|
||||
SPELL_PALADIN_SWIFT_RETRIBUTION_R1 = 53379,
|
||||
|
||||
@@ -2133,6 +2139,70 @@ private:
|
||||
uint32 _spellId;
|
||||
};
|
||||
|
||||
// 63510 - Improved Concentration Aura (requires Concentration Aura on the target)
|
||||
// 63514 - Improved Devotion Aura (requires Devotion Aura on the target)
|
||||
class spell_pal_improved_aura_effect : public AuraScript
|
||||
{
|
||||
PrepareAuraScript(spell_pal_improved_aura_effect);
|
||||
|
||||
public:
|
||||
spell_pal_improved_aura_effect(uint32 auraSpellId) : AuraScript(), _auraSpellId(auraSpellId) { }
|
||||
|
||||
bool Validate(SpellInfo const* /*spellInfo*/) override
|
||||
{
|
||||
return ValidateSpellInfo({ _auraSpellId });
|
||||
}
|
||||
|
||||
bool CheckAreaTarget(Unit* target)
|
||||
{
|
||||
return target->GetAuraOfRankedSpell(_auraSpellId, GetCasterGUID());
|
||||
}
|
||||
|
||||
void Register() override
|
||||
{
|
||||
DoCheckAreaTarget += AuraCheckAreaTargetFn(spell_pal_improved_aura_effect::CheckAreaTarget);
|
||||
}
|
||||
|
||||
private:
|
||||
uint32 _auraSpellId;
|
||||
};
|
||||
|
||||
// 63531 - Sanctified Retribution
|
||||
// "Targets affected by any of your auras" - shared effect of Sanctified Retribution and Swift Retribution
|
||||
class spell_pal_sanctified_retribution_effect : public AuraScript
|
||||
{
|
||||
PrepareAuraScript(spell_pal_sanctified_retribution_effect);
|
||||
|
||||
bool Validate(SpellInfo const* /*spellInfo*/) override
|
||||
{
|
||||
return ValidateSpellInfo(
|
||||
{
|
||||
SPELL_PALADIN_DEVOTION_AURA_R1,
|
||||
SPELL_PALADIN_RETRIBUTION_AURA_R1,
|
||||
SPELL_PALADIN_CONCENTRACTION_AURA,
|
||||
SPELL_PALADIN_SHADOW_RESISTANCE_AURA_R1,
|
||||
SPELL_PALADIN_FROST_RESISTANCE_AURA_R1,
|
||||
SPELL_PALADIN_FIRE_RESISTANCE_AURA_R1,
|
||||
SPELL_PALADIN_CRUSADER_AURA
|
||||
});
|
||||
}
|
||||
|
||||
bool CheckAreaTarget(Unit* target)
|
||||
{
|
||||
for (uint32 auraSpellId : { SPELL_PALADIN_DEVOTION_AURA_R1, SPELL_PALADIN_RETRIBUTION_AURA_R1, SPELL_PALADIN_CONCENTRACTION_AURA,
|
||||
SPELL_PALADIN_SHADOW_RESISTANCE_AURA_R1, SPELL_PALADIN_FROST_RESISTANCE_AURA_R1, SPELL_PALADIN_FIRE_RESISTANCE_AURA_R1, SPELL_PALADIN_CRUSADER_AURA })
|
||||
if (target->GetAuraOfRankedSpell(auraSpellId, GetCasterGUID()))
|
||||
return true;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
void Register() override
|
||||
{
|
||||
DoCheckAreaTarget += AuraCheckAreaTargetFn(spell_pal_sanctified_retribution_effect::CheckAreaTarget);
|
||||
}
|
||||
};
|
||||
|
||||
// 53651 - Light's Beacon - Beacon of Light
|
||||
// Each source heal has a dedicated beacon copy spell:
|
||||
// 53652 - Holy Light, 53653 - Flash of Light, 53654 - Holy Shock
|
||||
@@ -2256,5 +2326,8 @@ void AddSC_paladin_spell_scripts()
|
||||
RegisterSpellScriptWithArgs(spell_pal_improved_aura, "spell_pal_improved_devotion_aura", SPELL_PALADIN_IMPROVED_DEVOTION_AURA);
|
||||
RegisterSpellScriptWithArgs(spell_pal_improved_aura, "spell_pal_sanctified_retribution", SPELL_PALADIN_SANCTIFIED_RETRIBUTION_AURA);
|
||||
RegisterSpellScriptWithArgs(spell_pal_improved_aura, "spell_pal_swift_retribution", SPELL_PALADIN_SANCTIFIED_RETRIBUTION_AURA);
|
||||
RegisterSpellScriptWithArgs(spell_pal_improved_aura_effect, "spell_pal_improved_concentraction_aura_effect", SPELL_PALADIN_CONCENTRACTION_AURA);
|
||||
RegisterSpellScriptWithArgs(spell_pal_improved_aura_effect, "spell_pal_improved_devotion_aura_effect", SPELL_PALADIN_DEVOTION_AURA_R1);
|
||||
RegisterSpellScript(spell_pal_sanctified_retribution_effect);
|
||||
RegisterSpellScript(spell_pal_light_s_beacon);
|
||||
}
|
||||
|
||||
@@ -355,42 +355,33 @@ private:
|
||||
std::unordered_map<ObjectGuid, Milliseconds> _combatTimer;
|
||||
};
|
||||
|
||||
struct npc_target_dummy : NullCreatureAI
|
||||
struct npc_target_dummy : ScriptedAI
|
||||
{
|
||||
npc_target_dummy(Creature* creature) : NullCreatureAI(creature)
|
||||
{
|
||||
_deathTimer = 15s;
|
||||
}
|
||||
explicit npc_target_dummy(Creature* creature) : ScriptedAI(creature) { }
|
||||
|
||||
void Reset() override
|
||||
{
|
||||
scheduler.CancelAll();
|
||||
ClearUniqueTimedEventsDone();
|
||||
|
||||
me->SetControlled(true, UNIT_STATE_STUNNED);
|
||||
me->SetLootRecipient(me->GetOwner());
|
||||
me->SelectLevel();
|
||||
}
|
||||
|
||||
void DamageTaken(Unit*, uint32& damage, DamageEffectType, SpellSchoolMask) override
|
||||
{
|
||||
damage = 0;
|
||||
ScheduleUniqueTimedEvent(15s, [this]
|
||||
{
|
||||
me->SetLootRecipient(me->GetOwner()); // the dummy is lootable by the player who summoned it
|
||||
me->LowerPlayerDamageReq(me->GetMaxHealth());
|
||||
me->KillSelf();
|
||||
}, 1);
|
||||
}
|
||||
|
||||
void UpdateAI(uint32 diff) override
|
||||
{
|
||||
scheduler.Update(diff);
|
||||
|
||||
if (!me->HasUnitState(UNIT_STATE_STUNNED))
|
||||
me->SetControlled(true, UNIT_STATE_STUNNED);
|
||||
|
||||
_deathTimer -= Milliseconds(diff);
|
||||
if (_deathTimer <= 0s)
|
||||
{
|
||||
me->SetLootRecipient(me->GetOwner());
|
||||
me->LowerPlayerDamageReq(me->GetMaxHealth());
|
||||
me->KillSelf();
|
||||
_deathTimer = 600s;
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
Milliseconds _deathTimer;
|
||||
};
|
||||
|
||||
/*########
|
||||
|
||||
@@ -36,6 +36,7 @@ enum ZeppelinEvent
|
||||
EVENT_UC_TO_OG_DEPARTURE = 15321,
|
||||
EVENT_UC_TO_GROMGOL_DEPARTURE = 15313,
|
||||
EVENT_GROMGOL_TO_UC_DEPARTURE = 15315,
|
||||
EVENT_WK_DEPARTURE = 15430,
|
||||
};
|
||||
|
||||
enum ZeppelinMaster
|
||||
@@ -55,6 +56,11 @@ enum ZeppelinMaster
|
||||
NPC_KRENDLE_BIGPOCKETS = 34766,
|
||||
};
|
||||
|
||||
enum ZeppelinTransport
|
||||
{
|
||||
GO_WESTGUARD_ZEPPELIN = 186371,
|
||||
};
|
||||
|
||||
const float SEARCH_RANGE_ZEPPELIN_MASTER = 32.0f;
|
||||
|
||||
enum ZeppelinPassenger
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user