Merge pull request #7 from azerothcore/master

Merge
This commit is contained in:
kadeshar
2025-08-27 21:04:10 +02:00
committed by GitHub
79 changed files with 2648 additions and 291 deletions
+4 -5
View File
@@ -29,6 +29,7 @@
#include "Config.h"
#include "DatabaseEnv.h"
#include "DatabaseLoader.h"
#include "GitRevision.h"
#include "IPLocation.h"
#include "IoContext.h"
#include "Log.h"
@@ -75,7 +76,7 @@ int main(int argc, char** argv)
auto vm = GetConsoleArguments(argc, argv, configFile);
// exit if help or version is enabled
if (vm.count("help"))
if (vm.count("help") || vm.count("version"))
return 0;
// Add file and args in config
@@ -292,13 +293,11 @@ variables_map GetConsoleArguments(int argc, char** argv, fs::path& configFile)
}
if (variablesMap.count("help"))
{
std::cout << all << "\n";
}
else if (variablesMap.count("version"))
std::cout << GitRevision::GetFullVersion() << "\n";
else if (variablesMap.count("dry-run"))
{
sConfigMgr->setDryRun(true);
}
return variablesMap;
}
+3 -5
View File
@@ -127,7 +127,7 @@ int main(int argc, char** argv)
auto vm = GetConsoleArguments(argc, argv, configFile, configService);
// exit if help or version is enabled
if (vm.count("help"))
if (vm.count("help") || vm.count("version"))
return 0;
#if AC_PLATFORM == AC_PLATFORM_WINDOWS
@@ -744,13 +744,11 @@ variables_map GetConsoleArguments(int argc, char** argv, fs::path& configFile, [
}
if (vm.count("help"))
{
std::cout << all << "\n";
}
else if (vm.count("version"))
std::cout << GitRevision::GetFullVersion() << "\n";
else if (vm.count("dry-run"))
{
sConfigMgr->setDryRun(true);
}
return vm;
}
@@ -657,7 +657,7 @@ Appender.Console=1,4,0,"1 9 3 6 5 8"
Appender.Server=2,5,0,Server.log,w
Appender.Playerbots=2,5,0,Playerbots.log,w
# Appender.GM=2,5,15,gm_%s.log
Appender.Errors=2,5,0,Errors.log,w
Appender.Errors=2,2,0,Errors.log,w
# Appender.DB=3,5,0
# Logger config values: Given a logger "name"
@@ -686,6 +686,7 @@ Logger.mmaps=4,Server
Logger.scripts.hotswap=4,Console Server
Logger.server=4,Console Server
Logger.sql.sql=2,Console Errors
Logger.sql.updates=4,Console Server Errors
Logger.sql=4,Console Server
Logger.time.update=4,Console Server
Logger.module=4,Console Server
@@ -785,7 +786,6 @@ Logger.playerbots=5,Console Playerbots
#Logger.spells=4,Console Server
#Logger.sql.dev=4,Console Server
#Logger.sql.driver=4,Console Server
#Logger.sql.updates=4,Console Server
#Logger.vehicles=4,Console Server
#Logger.warden=4,Console Server
#Logger.weather=4,Console Server
@@ -3143,6 +3143,16 @@ LeaveGroupOnLogout.Enabled = 0
Group.Raid.LevelRestriction = 10
#
# Group.RandomRollMaximum
#
# The maximum value for use with the client '/roll' command.
# Blizzlike and maximum value is 1000000. (Based on Classic and 3.3.5a client testing respectively)
# Default: 1000000
#
Group.RandomRollMaximum = 1000000
#
###################################################################################################
@@ -607,7 +607,7 @@ void CharacterDatabaseConnection::DoPrepareStatements()
PrepareStatement(CHAR_INS_PROFANITY_PLAYER_NAME, "INSERT IGNORE INTO profanity_name (name) VALUES (?)", CONNECTION_ASYNC);
// Character settings
PrepareStatement(CHAR_SEL_CHAR_SETTINGS, "SELECT source, data FROM character_settings WHERE guid = ?", CONNECTION_ASYNC);
PrepareStatement(CHAR_SEL_CHAR_SETTINGS, "SELECT source, data FROM character_settings WHERE guid = ?", CONNECTION_BOTH);
PrepareStatement(CHAR_REP_CHAR_SETTINGS, "REPLACE INTO character_settings (guid, source, data) VALUES (?, ?, ?)", CONNECTION_ASYNC);
PrepareStatement(CHAR_DEL_CHAR_SETTINGS, "DELETE FROM character_settings WHERE guid = ?", CONNECTION_ASYNC);
@@ -72,6 +72,8 @@ SmartAI::SmartAI(Creature* c) : CreatureAI(c)
mcanSpawn = true;
_chaseOnInterrupt = false;
// Xinef: Vehicle conditions
m_ConditionsTimer = 0;
if (me->GetVehicleKit())
@@ -212,6 +212,9 @@ public:
// Xinef
void SetWPPauseTimer(uint32 time) { mWPPauseTimer = time; }
void SetChaseOnInterrupt(bool apply) { _chaseOnInterrupt = apply; }
[[nodiscard]] bool CanChaseOnInterrupt() const { return _chaseOnInterrupt; }
private:
bool mIsCharmed;
uint32 mFollowCreditType;
@@ -257,6 +260,8 @@ private:
void CheckConditions(const uint32 diff);
ConditionList conditions;
uint32 m_ConditionsTimer;
bool _chaseOnInterrupt;
};
class SmartGameObjectAI : public GameObjectAI
@@ -742,6 +742,8 @@ void SmartScript::ProcessAction(SmartScriptHolder& e, Unit* unit, uint32 var0, u
if (e.action.cast.castFlags & SMARTCAST_COMBAT_MOVE)
{
CAST_AI(SmartAI, me->AI())->SetChaseOnInterrupt(true);
if (!me->isMoving()) // Don't try to reposition while we are moving
{
// If cast flag SMARTCAST_COMBAT_MOVE is set combat movement will not be allowed unless target is outside spell range, out of mana, or LOS.
@@ -893,7 +895,7 @@ void SmartScript::ProcessAction(SmartScriptHolder& e, Unit* unit, uint32 var0, u
go->SetLootState(GO_READY);
}
go->UseDoorOrButton(0, !!e.action.activateObject.alternative, unit);
go->UseDoorOrButton(0, e.action.activateObject.alternative, unit);
LOG_DEBUG("sql.sql", "SmartScript::ProcessAction:: SMART_ACTION_ACTIVATE_GOBJECT. Gameobject {} activated", go->GetGUID().ToString());
}
}
@@ -1483,14 +1485,14 @@ void SmartScript::ProcessAction(SmartScriptHolder& e, Unit* unit, uint32 var0, u
{
for (WorldObject* target : targets)
if (IsUnit(target))
target->ToUnit()->SetVisible(!!e.action.visibility.state);
target->ToUnit()->SetVisible(e.action.visibility.state);
break;
}
case SMART_ACTION_SET_ACTIVE:
{
for (WorldObject* target : targets)
target->setActive(!!e.action.setActive.state);
target->setActive(e.action.setActive.state);
break;
}
case SMART_ACTION_ATTACK_START:
@@ -202,10 +202,29 @@ void SmartAIMgr::LoadSmartAIFromDB()
}
else
{
if (!sObjectMgr->GetCreatureData(uint32(std::abs(temp.entryOrGuid))))
switch (source_type)
{
LOG_ERROR("sql.sql", "SmartAIMgr::LoadSmartAIFromDB: Creature guid ({}) does not exist, skipped loading.", uint32(std::abs(temp.entryOrGuid)));
continue;
case SMART_SCRIPT_TYPE_CREATURE:
{
if (!sObjectMgr->GetCreatureData(uint32(std::abs(temp.entryOrGuid))))
{
LOG_ERROR("sql.sql", "SmartAIMgr::LoadSmartAIFromDB: Creature guid ({}) does not exist, skipped loading.", uint32(std::abs(temp.entryOrGuid)));
continue;
}
break;
}
case SMART_SCRIPT_TYPE_GAMEOBJECT:
{
if (!sObjectMgr->GetGameObjectData(uint32(std::abs(temp.entryOrGuid))))
{
LOG_ERROR("sql.sql", "SmartAIMgr::LoadSmartAIFromDB: GameObject guid ({}) does not exist, skipped loading.", uint32(temp.entryOrGuid));
continue;
}
break;
}
default:
LOG_ERROR("sql.sql", "SmartAIMgr::LoadSmartAIFromDB: not yet implemented source_type {}", (uint32)source_type);
continue;
}
}
@@ -488,7 +488,7 @@ void AuctionHouseObject::AddAuction(AuctionEntry* auction)
bool AuctionHouseObject::RemoveAuction(AuctionEntry* auction)
{
bool wasInMap = !!_auctionsMap.erase(auction->Id);
bool wasInMap = _auctionsMap.erase(auction->Id);
sAuctionMgr->GetAuctionHouseSearcher()->RemoveAuction(auction);
sScriptMgr->OnAuctionRemove(this, auction);
+1 -1
View File
@@ -206,7 +206,7 @@ bool Acore::Hyperlinks::LinkTags::spell::StoreTo(SpellInfo const*& val, std::str
if (!(t.TryConsumeTo(spellId) && t.IsEmpty()))
return false;
return !!(val = sSpellMgr->GetSpellInfo(spellId));
return (val = sSpellMgr->GetSpellInfo(spellId));
}
bool Acore::Hyperlinks::LinkTags::talent::StoreTo(TalentLinkData& val, std::string_view text)
+1 -1
View File
@@ -68,7 +68,7 @@ bool Condition::Meets(ConditionSourceInfo& sourceInfo)
{
// don't allow 0 items (it's checked during table load)
ASSERT(ConditionValue2);
bool checkBank = !!ConditionValue3;
bool checkBank = ConditionValue3;
condMeets = player->HasItemCount(ConditionValue1, ConditionValue2, checkBank);
}
}
@@ -1987,8 +1987,6 @@ void Creature::setDeathState(DeathState state, bool despawn)
Dismount(); // if creature is mounted on a virtual mount, remove it at death
setActive(false);
if (HasSearchedAssistance())
{
SetNoSearchAssistance(false);
@@ -3904,7 +3902,7 @@ bool Creature::IsUpdateNeeded()
if (IsInCombat())
return true;
if (IsVisibilityOverridden())
if (!GetObjectVisibilityContainer().GetVisiblePlayersMap().empty())
return true;
if (ToTempSummon())
@@ -67,7 +67,7 @@ struct FormationInfo
uint32 point_1;
uint32 point_2;
bool HasGroupFlag(uint16 flag) const { return !!(groupAI & flag); }
bool HasGroupFlag(uint16 flag) const { return (groupAI & flag); }
};
typedef std::unordered_map<ObjectGuid::LowType/*memberDBGUID*/, FormationInfo /*formationInfo*/> CreatureGroupInfoType;
@@ -3086,7 +3086,7 @@ bool GameObject::IsUpdateNeeded()
if (GetMap()->isCellMarked(GetCurrentCell().GetCellCoord().GetId()))
return true;
if (IsVisibilityOverridden())
if (!GetObjectVisibilityContainer().GetVisiblePlayersMap().empty())
return true;
if (IsTransport())
@@ -156,8 +156,8 @@ public:
void SaveToDB(bool saveAddon = false);
void SaveToDB(uint32 mapid, uint8 spawnMask, uint32 phaseMask, bool saveAddon = false);
bool LoadFromDB(ObjectGuid::LowType guid, Map* map) { return LoadGameObjectFromDB(guid, map, false); }
bool LoadGameObjectFromDB(ObjectGuid::LowType guid, Map* map, bool addToMap = true);
virtual bool LoadFromDB(ObjectGuid::LowType guid, Map* map) { return LoadGameObjectFromDB(guid, map, false); }
virtual bool LoadGameObjectFromDB(ObjectGuid::LowType guid, Map* map, bool addToMap = true);
void DeleteFromDB();
void SetOwnerGUID(ObjectGuid owner)
+7 -1
View File
@@ -1696,7 +1696,7 @@ bool WorldObject::CanSeeOrDetect(WorldObject const* obj, bool ignoreStealth, boo
if (this == obj)
return true;
if (obj->IsNeverVisible() || CanNeverSee(obj))
if (CanNeverSee(obj))
return false;
if (obj->IsAlwaysVisibleFor(this) || CanAlwaysSee(obj))
@@ -1840,6 +1840,12 @@ bool WorldObject::CanSeeOrDetect(WorldObject const* obj, bool ignoreStealth, boo
bool WorldObject::CanNeverSee(WorldObject const* obj) const
{
if (!IsInWorld())
return true;
if (obj->IsNeverVisible())
return true;
if (IsCreature() && obj->IsCreature())
return GetMap() != obj->GetMap() || (!InSamePhase(obj) && ToUnit()->GetVehicleBase() != obj && this != obj->ToUnit()->GetVehicleBase());
return GetMap() != obj->GetMap() || !InSamePhase(obj);
@@ -66,6 +66,10 @@ void ObjectVisibilityContainer::LinkWorldObjectVisibility(WorldObject* worldObje
if (worldObject == _selfObject)
return;
// Transports are special and should not be added to our visibility map
if (worldObject->IsGameObject() && worldObject->ToGameObject()->IsTransport())
return;
// Only players can link visibility
if (!_visibleWorldObjectsMap)
return;
@@ -4552,6 +4552,9 @@ void Player::ResurrectPlayer(float restore_percent, bool applySickness)
// update visibility
UpdateObjectVisibility();
// recast lost by death auras of any items held in the inventory
CastAllObtainSpells();
sScriptMgr->OnPlayerResurrect(this, restore_percent, applySickness);
if (!applySickness)
@@ -6019,6 +6022,7 @@ void Player::RewardReputation(Unit* victim)
if (Rep->RepFaction1 && (!Rep->TeamDependent || teamId == TEAM_ALLIANCE))
{
float donerep1 = CalculateReputationGain(REPUTATION_SOURCE_KILL, victim->GetLevel(), static_cast<float>(Rep->RepValue1), ChampioningFaction ? ChampioningFaction : Rep->RepFaction1);
sScriptMgr->OnPlayerGiveReputation(this, Rep->RepFaction1, donerep1, REPUTATION_SOURCE_KILL);
FactionEntry const* factionEntry1 = sFactionStore.LookupEntry(ChampioningFaction ? ChampioningFaction : Rep->RepFaction1);
if (factionEntry1)
@@ -6030,6 +6034,7 @@ void Player::RewardReputation(Unit* victim)
if (Rep->RepFaction2 && (!Rep->TeamDependent || teamId == TEAM_HORDE))
{
float donerep2 = CalculateReputationGain(REPUTATION_SOURCE_KILL, victim->GetLevel(), static_cast<float>(Rep->RepValue2), ChampioningFaction ? ChampioningFaction : Rep->RepFaction2);
sScriptMgr->OnPlayerGiveReputation(this, Rep->RepFaction2, donerep2, REPUTATION_SOURCE_KILL);
FactionEntry const* factionEntry2 = sFactionStore.LookupEntry(ChampioningFaction ? ChampioningFaction : Rep->RepFaction2);
if (factionEntry2)
@@ -6069,22 +6074,27 @@ void Player::RewardReputation(Quest const* quest)
if (quest->IsDaily())
{
rep = CalculateReputationGain(REPUTATION_SOURCE_DAILY_QUEST, GetQuestLevel(quest), rep, quest->RewardFactionId[i], false);
sScriptMgr->OnPlayerGiveReputation(this, quest->RewardFactionId[i], rep, REPUTATION_SOURCE_DAILY_QUEST);
}
else if (quest->IsWeekly())
{
rep = CalculateReputationGain(REPUTATION_SOURCE_WEEKLY_QUEST, GetQuestLevel(quest), rep, quest->RewardFactionId[i], false);
sScriptMgr->OnPlayerGiveReputation(this, quest->RewardFactionId[i], rep, REPUTATION_SOURCE_WEEKLY_QUEST);
}
else if (quest->IsMonthly())
{
rep = CalculateReputationGain(REPUTATION_SOURCE_MONTHLY_QUEST, GetQuestLevel(quest), rep, quest->RewardFactionId[i], false);
sScriptMgr->OnPlayerGiveReputation(this, quest->RewardFactionId[i], rep, REPUTATION_SOURCE_MONTHLY_QUEST);
}
else if (quest->IsRepeatable())
{
rep = CalculateReputationGain(REPUTATION_SOURCE_REPEATABLE_QUEST, GetQuestLevel(quest), rep, quest->RewardFactionId[i], false);
sScriptMgr->OnPlayerGiveReputation(this, quest->RewardFactionId[i], rep, REPUTATION_SOURCE_REPEATABLE_QUEST);
}
else
{
rep = CalculateReputationGain(REPUTATION_SOURCE_QUEST, GetQuestLevel(quest), rep, quest->RewardFactionId[i], false);
sScriptMgr->OnPlayerGiveReputation(this, quest->RewardFactionId[i], rep, REPUTATION_SOURCE_QUEST);
}
if (FactionEntry const* factionEntry = sFactionStore.LookupEntry(quest->RewardFactionId[i]))
@@ -7028,6 +7038,46 @@ void Player::_ApplyWeaponDamage(uint8 slot, ItemTemplate const* proto, ScalingSt
UpdateDamagePhysical(WeaponAttackType(attType));
}
void Player::CastAllObtainSpells()
{
for (uint8 slot = INVENTORY_SLOT_ITEM_START; slot < INVENTORY_SLOT_ITEM_END; ++slot)
if (Item* item = GetItemByPos(INVENTORY_SLOT_BAG_0, slot))
ApplyItemObtainSpells(item, true);
for (uint8 i = INVENTORY_SLOT_BAG_START; i < INVENTORY_SLOT_BAG_END; ++i)
{
Bag* bag = GetBagByPos(i);
if (!bag)
continue;
for (uint32 slot = 0; slot < bag->GetBagSize(); ++slot)
if (Item* item = bag->GetItemByPos(slot))
ApplyItemObtainSpells(item, true);
}
}
void Player::ApplyItemObtainSpells(Item* item, bool apply)
{
ItemTemplate const* itemTemplate = item->GetTemplate();
for (uint8 i = 0; i < MAX_ITEM_PROTO_SPELLS; ++i)
{
if (itemTemplate->Spells[i].SpellTrigger != ITEM_SPELLTRIGGER_ON_NO_DELAY_USE) // On obtain trigger
continue;
int32 const spellId = itemTemplate->Spells[i].SpellId;
if (spellId <= 0)
continue;
if (apply)
{
if (!HasAura(spellId))
CastSpell(this, spellId, true, item);
}
else
RemoveAurasDueToSpell(spellId);
}
}
SpellSchoolMask Player::GetMeleeDamageSchoolMask(WeaponAttackType attackType /*= BASE_ATTACK*/, uint8 damageIndex /*= 0*/) const
{
if (Item const* weapon = GetWeaponForAttack(attackType, true))
+6 -14
View File
@@ -235,17 +235,6 @@ enum ActionButtonType
ACTION_BUTTON_ITEM = 0x80
};
enum ReputationSource
{
REPUTATION_SOURCE_KILL,
REPUTATION_SOURCE_QUEST,
REPUTATION_SOURCE_DAILY_QUEST,
REPUTATION_SOURCE_WEEKLY_QUEST,
REPUTATION_SOURCE_MONTHLY_QUEST,
REPUTATION_SOURCE_REPEATABLE_QUEST,
REPUTATION_SOURCE_SPELL
};
enum QuestSound
{
QUEST_SOUND_FAILURE = 847
@@ -1682,7 +1671,7 @@ public:
bool RemoveMItem(ObjectGuid::LowType itemLowGuid)
{
return !!mMitems.erase(itemLowGuid);
return mMitems.erase(itemLowGuid);
}
void PetSpellInitialize();
@@ -2208,6 +2197,9 @@ public:
void ResetAllPowers();
void CastAllObtainSpells();
void ApplyItemObtainSpells(Item* item, bool apply);
SpellSchoolMask GetMeleeDamageSchoolMask(WeaponAttackType attackType = BASE_ATTACK, uint8 damageIndex = 0) const override;
void _ApplyWeaponDependentAuraMods(Item* item, WeaponAttackType attackType, bool apply);
@@ -2632,8 +2624,8 @@ public:
std::string GetPlayerName();
// Settings
[[nodiscard]] PlayerSetting GetPlayerSetting(std::string const& source, uint8 index);
void UpdatePlayerSetting(std::string const& source, uint8 index, uint32 value);
[[nodiscard]] PlayerSetting GetPlayerSetting(std::string const& source, uint32 index);
void UpdatePlayerSetting(std::string const& source, uint32 index, uint32 value);
void SendSystemMessage(std::string_view msg, bool escapeCharacters = false);
@@ -18,11 +18,95 @@
#include "Player.h"
#include "StringConvert.h"
#include "Tokenize.h"
#include "CharacterDatabase.h"
/*********************************************************/
/*** PLAYER SETTINGS SYSTEM ***/
/*********************************************************/
namespace PlayerSettingsStore
{
// Common helper: parse space-separated data string into PlayerSettingVector
PlayerSettingVector ParseSettingsData(std::string const& data)
{
PlayerSettingVector result;
std::vector<std::string_view> tokens = Acore::Tokenize(data, ' ', false);
result.reserve(tokens.size());
for (auto const& token : tokens)
{
if (token.empty())
continue;
if (auto parsed = Acore::StringTo<uint32>(token))
result.emplace_back(*parsed);
}
return result;
}
// Common helper: serialize PlayerSettingVector to space-separated string
std::string SerializeSettingsData(PlayerSettingVector const& settings)
{
if (settings.empty())
return "";
std::ostringstream data;
data << settings[0].value;
for (size_t i = 1; i < settings.size(); ++i)
data << ' ' << settings[i].value;
return data.str();
}
// helper: load a single source row for a player and parse to vector
static PlayerSettingVector LoadPlayerSettings(uint32 playerLowGuid, std::string const& source)
{
PlayerSettingVector result;
CharacterDatabasePreparedStatement* stmt = CharacterDatabase.GetPreparedStatement(CHAR_SEL_CHAR_SETTINGS);
stmt->SetData(0, playerLowGuid);
PreparedQueryResult dbRes = CharacterDatabase.Query(stmt);
if (!dbRes)
return result;
do
{
Field* fields = dbRes->Fetch();
std::string rowSource = fields[0].Get<std::string>();
if (rowSource != source)
continue;
std::string data = fields[1].Get<std::string>();
return ParseSettingsData(data);
} while (dbRes->NextRow());
return result;
}
void UpdateSetting(uint32 playerLowGuid, std::string const& source, uint32 index, uint32 value)
{
if (!sWorld->getBoolConfig(CONFIG_PLAYER_SETTINGS_ENABLED))
return;
PlayerSettingVector settings = LoadPlayerSettings(playerLowGuid, source);
size_t const requiredSize = static_cast<size_t>(index) + 1;
if (settings.size() < requiredSize)
settings.resize(requiredSize); // zero-initialized PlayerSetting::value
settings[index].value = value;
CharacterDatabasePreparedStatement* stmt = PlayerSettingsStore::PrepareReplaceStatement(playerLowGuid, source, settings);
CharacterDatabase.Execute(stmt);
}
}
// Implementation of PrepareReplaceStatement
CharacterDatabasePreparedStatement* PlayerSettingsStore::PrepareReplaceStatement(uint32 playerLowGuid, std::string const& source, PlayerSettingVector const& settings)
{
CharacterDatabasePreparedStatement* stmt = CharacterDatabase.GetPreparedStatement(CHAR_REP_CHAR_SETTINGS);
stmt->SetData(0, playerLowGuid);
stmt->SetData(1, source);
stmt->SetData(2, SerializeSettingsData(settings));
return stmt;
}
void Player::_LoadCharacterSettings(PreparedQueryResult result)
{
m_charSettingsMap.clear();
@@ -39,27 +123,13 @@ void Player::_LoadCharacterSettings(PreparedQueryResult result)
std::string source = fields[0].Get<std::string>();
std::string data = fields[1].Get<std::string>();
std::vector<std::string_view> tokens = Acore::Tokenize(data, ' ', false);
PlayerSettingVector settings;
settings.reserve(tokens.size()); // reserve capacity but don't resize
for (auto const& token : tokens)
{
if (token.empty())
continue;
// Try to parse the value safely
if (auto parsed = Acore::StringTo<uint32>(token))
settings.emplace_back(*parsed);
}
PlayerSettingVector settings = PlayerSettingsStore::ParseSettingsData(data);
m_charSettingsMap.emplace(std::move(source), std::move(settings));
} while (result->NextRow());
}
PlayerSetting Player::GetPlayerSetting(std::string const& source, uint8 index)
PlayerSetting Player::GetPlayerSetting(std::string const& source, uint32 index)
{
auto it = m_charSettingsMap.find(source);
if (it == m_charSettingsMap.end() || static_cast<size_t>(index) >= it->second.size())
@@ -81,21 +151,12 @@ void Player::_SavePlayerSettings(CharacterDatabaseTransaction trans)
if (settings.empty())
continue;
std::ostringstream data;
data << settings[0].value;
for (size_t i = 1; i < settings.size(); ++i)
data << ' ' << settings[i].value;
CharacterDatabasePreparedStatement* stmt = CharacterDatabase.GetPreparedStatement(CHAR_REP_CHAR_SETTINGS);
stmt->SetData(0, GetGUID().GetCounter());
stmt->SetData(1, source);
stmt->SetData(2, data.str());
CharacterDatabasePreparedStatement* stmt = PlayerSettingsStore::PrepareReplaceStatement(GetGUID().GetCounter(), source, settings);
trans->Append(stmt);
}
}
void Player::UpdatePlayerSetting(std::string const& source, uint8 index, uint32 value)
void Player::UpdatePlayerSetting(std::string const& source, uint32 index, uint32 value)
{
auto it = m_charSettingsMap.find(source);
size_t const requiredSize = static_cast<size_t>(index) + 1;
@@ -17,6 +17,7 @@
#ifndef _PLAYER_SETTINGS_H
#define _PLAYER_SETTINGS_H
#include "DatabaseEnvFwd.h"
class Player;
@@ -51,4 +52,18 @@ struct PlayerSetting
typedef std::vector<PlayerSetting> PlayerSettingVector;
typedef std::map<std::string, PlayerSettingVector> PlayerSettingMap;
// Standalone API: update a player's setting directly on DB by GUID (low part) without requiring a Player instance
namespace PlayerSettingsStore
{
// Update a single setting value for any player by GUID (works for online or offline players).
// This reads the existing "source" row from character_settings, adjusts the index, and REPLACE's it back.
void UpdateSetting(uint32 playerLowGuid, std::string const& source, uint32 index, uint32 value);
// Common helpers for parsing and serializing settings data
PlayerSettingVector ParseSettingsData(std::string const& data);
std::string SerializeSettingsData(PlayerSettingVector const& settings);
// Prepare a REPLACE statement populated with given settings data. Caller may execute or append to a transaction.
CharacterDatabasePreparedStatement* PrepareReplaceStatement(uint32 playerLowGuid, std::string const& source, PlayerSettingVector const& settings);
}
#endif
@@ -2577,8 +2577,6 @@ Item* Player::StoreItem(ItemPosCountVec const& dest, Item* pItem, bool update)
return nullptr;
Item* lastItem = pItem;
ItemTemplate const* proto = pItem->GetTemplate();
for (ItemPosCountVec::const_iterator itr = dest.begin(); itr != dest.end();)
{
uint16 pos = itr->pos;
@@ -2595,13 +2593,6 @@ Item* Player::StoreItem(ItemPosCountVec const& dest, Item* pItem, bool update)
lastItem = _StoreItem(pos, pItem, count, true, update);
}
// cast after item storing - some checks in checkcast requires item to be present!!
if (lastItem)
for (uint8 i = 0; i < MAX_ITEM_PROTO_SPELLS; ++i)
if (proto->Spells[i].SpellTrigger == ITEM_SPELLTRIGGER_ON_NO_DELAY_USE && proto->Spells[i].SpellId > 0) // On obtain trigger
if (!HasAura(proto->Spells[i].SpellId))
CastSpell(this, proto->Spells[i].SpellId, true, lastItem);
return lastItem;
}
@@ -2664,6 +2655,9 @@ Item* Player::_StoreItem(uint16 pos, Item* pItem, uint32 count, bool clone, bool
AddEnchantmentDurations(pItem);
AddItemDurations(pItem);
if (bag == INVENTORY_SLOT_BAG_0 || (bag >= INVENTORY_SLOT_BAG_START && bag < INVENTORY_SLOT_BAG_END))
ApplyItemObtainSpells(pItem, true);
return pItem;
}
else
@@ -2700,6 +2694,9 @@ Item* Player::_StoreItem(uint16 pos, Item* pItem, uint32 count, bool clone, bool
pItem2->SetState(ITEM_CHANGED, this);
if (bag == INVENTORY_SLOT_BAG_0 || (bag >= INVENTORY_SLOT_BAG_START && bag < INVENTORY_SLOT_BAG_END))
ApplyItemObtainSpells(pItem2, true);
return pItem2;
}
}
@@ -3046,10 +3043,7 @@ void Player::DestroyItem(uint8 bag, uint8 slot, bool update)
pItem->ClearSoulboundTradeable(this);
RemoveTradeableItem(pItem);
ItemTemplate const* proto = pItem->GetTemplate();
for (uint8 i = 0; i < MAX_ITEM_PROTO_SPELLS; ++i)
if (proto->Spells[i].SpellTrigger == ITEM_SPELLTRIGGER_ON_NO_DELAY_USE && proto->Spells[i].SpellId > 0) // On obtain trigger
RemoveAurasDueToSpell(proto->Spells[i].SpellId);
ApplyItemObtainSpells(pItem, false);
ItemRemovedQuestCheck(pItem->GetEntry(), pItem->GetCount());
@@ -3102,8 +3096,9 @@ void Player::DestroyItem(uint8 bag, uint8 slot, bool update)
pBag->RemoveItem(slot, update);
// Xinef: item is removed, remove loot from storage if any
if (proto->HasFlag(ITEM_FLAG_HAS_LOOT))
sLootItemStorage->RemoveStoredLoot(pItem->GetGUID());
if (ItemTemplate const* proto = pItem->GetTemplate())
if (proto->HasFlag(ITEM_FLAG_HAS_LOOT))
sLootItemStorage->RemoveStoredLoot(pItem->GetGUID());
if (IsInWorld() && update)
{
@@ -1646,10 +1646,7 @@ template <>
inline void UpdateVisibilityOf_helper(Player* player, GameObject* target,
std::vector<Unit*>& /*v*/)
{
// @HACK: This is to prevent objects like deeprun tram from disappearing
// when player moves far from its spawn point while riding it
if ((target->GetGOInfo()->type != GAMEOBJECT_TYPE_TRANSPORT))
player->GetObjectVisibilityContainer().LinkWorldObjectVisibility(target);
player->GetObjectVisibilityContainer().LinkWorldObjectVisibility(target);
}
template <>
@@ -607,12 +607,12 @@ void MotionTransport::DelayedTeleportTransport()
}
Map* newMap = sMapMgr->CreateBaseMap(newMapId);
GetMap()->RemoveFromMap<MotionTransport>(this, false);
GetMap()->RemoveFromMap<Transport>(this, false);
newMap->LoadGrid(x, y); // xinef: load before adding passengers to new map
SetMap(newMap);
Relocate(x, y, z, o);
GetMap()->AddToMap<MotionTransport>(this);
GetMap()->AddToMap<Transport>(this);
LoadStaticPassengers();
}
@@ -690,6 +690,40 @@ StaticTransport::~StaticTransport()
ASSERT(_passengers.empty());
}
bool StaticTransport::LoadGameObjectFromDB(ObjectGuid::LowType spawnId, Map* map, bool addToMap)
{
GameObjectData const* data = sObjectMgr->GetGameObjectData(spawnId);
if (!data)
{
LOG_ERROR("sql.sql", "Gameobject (GUID: {}) not found in table `gameobject`, can't load. ", spawnId);
return false;
}
uint32 entry = data->id;
//uint32 map_id = data->mapid; // already used before call
uint32 phaseMask = data->phaseMask;
float x = data->posX;
float y = data->posY;
float z = data->posZ;
float ang = data->orientation;
uint32 animprogress = data->animprogress;
GOState go_state = data->go_state;
uint32 artKit = data->artKit;
m_goData = data;
m_spawnId = spawnId;
if (!Create(map->GenerateLowGuid<HighGuid::Transport>(), entry, map, phaseMask, x, y, z, ang, data->rotation, animprogress, go_state, artKit))
return false;
if (addToMap && !GetMap()->AddToMap<Transport>(this))
return false;
return true;
}
bool StaticTransport::Create(ObjectGuid::LowType guidlow, uint32 name_id, Map* map, uint32 phaseMask, float x, float y, float z, float ang, G3D::Quat const& rotation, uint32 animprogress, GOState go_state, uint32 artKit)
{
ASSERT(map);
@@ -794,7 +828,6 @@ bool StaticTransport::Create(ObjectGuid::LowType guidlow, uint32 name_id, Map* m
LastUsedScriptID = GetGOInfo()->ScriptId;
AIM_Initialize();
this->setActive(true);
return true;
}
@@ -929,7 +962,9 @@ void StaticTransport::UpdatePosition(float x, float y, float z, float o)
if (!GetMap()->IsGridLoaded(x, y)) // pussywizard: should not happen, but just in case
GetMap()->LoadGrid(x, y);
GetMap()->GameObjectRelocation(this, x, y, z, o); // this also relocates the model
Relocate(x, y, z, o);
UpdateModelPosition();
UpdatePassengerPositions();
}
@@ -38,6 +38,8 @@ public:
virtual void RemovePassenger(WorldObject* passenger, bool withAll = false) = 0;
PassengerSet const& GetPassengers() const { return _passengers; }
virtual void DelayedUpdate(uint32 /*diff*/) {}
uint32 GetPathProgress() const { return GetGOValue()->Transport.PathProgress; }
void SetPathProgress(uint32 val) { m_goValue.Transport.PathProgress = val; }
@@ -57,7 +59,7 @@ public:
void BuildUpdate(UpdateDataMapType& data_map) override;
void Update(uint32 diff) override;
void DelayedUpdate(uint32 diff);
void DelayedUpdate(uint32 diff) override;
void UpdatePosition(float x, float y, float z, float o);
void AddPassenger(WorldObject* passenger, bool withAll = false) override;
@@ -115,6 +117,8 @@ public:
StaticTransport();
~StaticTransport() override;
bool LoadFromDB(ObjectGuid::LowType guid, Map* map) override { return LoadGameObjectFromDB(guid, map, false); }
bool LoadGameObjectFromDB(ObjectGuid::LowType guid, Map* map, bool addToMap = true) override;
bool Create(ObjectGuid::LowType guidlow, uint32 name_id, Map* map, uint32 phaseMask, float x, float y, float z, float ang, G3D::Quat const& rotation, uint32 animprogress, GOState go_state, uint32 artKit = 0) override;
void CleanupsBeforeDelete(bool finalCleanup = true) override;
void BuildUpdate(UpdateDataMapType& data_map) override;
+12 -2
View File
@@ -53,6 +53,7 @@
#include "Player.h"
#include "ReputationMgr.h"
#include "ScriptMgr.h"
#include "SmartAI.h"
#include "Spell.h"
#include "SpellAuraEffects.h"
#include "SpellAuras.h"
@@ -4109,8 +4110,8 @@ void Unit::InterruptSpell(CurrentSpellTypes spellType, bool withDelayed, bool wi
//LOG_DEBUG("entities.unit", "Interrupt spell for unit {}.", GetEntry());
Spell* spell = m_currentSpells[spellType];
if (spell
&& (withDelayed || spell->getState() != SPELL_STATE_DELAYED)
&& (withInstant || spell->GetCastTime() > 0 || spell->getState() == SPELL_STATE_CASTING)) // xinef: or spell is in casting state (channeled spells only)
&& (withDelayed || spell->getState() != SPELL_STATE_DELAYED)
&& (withInstant || spell->GetCastTime() > 0 || spell->getState() == SPELL_STATE_CASTING)) // xinef: or spell is in casting state (channeled spells only)
{
// for example, do not let self-stun aura interrupt itself
if (!spell->IsInterruptable())
@@ -4128,6 +4129,15 @@ void Unit::InterruptSpell(CurrentSpellTypes spellType, bool withDelayed, bool wi
m_currentSpells[spellType] = nullptr;
spell->SetReferencedFromCurrent(false);
}
// SAI creatures only
// Start chasing victim if they are spell casters (at least one SMC spell) if interrupted/silenced.
if (IsCreature())
{
if (SmartAI* ai = dynamic_cast<SmartAI*>(ToCreature()->AI()))
if (ai->CanChaseOnInterrupt())
ai->SetCombatMove(true);
}
}
}
+19 -6
View File
@@ -64,15 +64,28 @@ void GridObjectLoader::LoadGameObjects(CellGuidSet const& guid_set, Map* map)
for (ObjectGuid::LowType const& guid : guid_set)
{
GameObjectData const* data = sObjectMgr->GetGameObjectData(guid);
GameObject* obj = data && sObjectMgr->IsGameObjectStaticTransport(data->id) ? new StaticTransport() : new GameObject();
if (!obj->LoadFromDB(guid, map))
if (data && sObjectMgr->IsGameObjectStaticTransport(data->id))
{
delete obj;
continue;
}
StaticTransport* transport = new StaticTransport();
AddObjectHelper<GameObject>(map, obj);
// Special case for static transports - we are loaded via grids
// but we do not want to actually be stored in the grid
if (!transport->LoadGameObjectFromDB(guid, map, true))
delete transport;
}
else
{
GameObject* obj = new GameObject();
if (!obj->LoadFromDB(guid, map))
{
delete obj;
continue;
}
AddObjectHelper<GameObject>(map, obj);
}
}
}
+11
View File
@@ -28,6 +28,7 @@
#include "LFGMgr.h"
#include "Log.h"
#include "MapMgr.h"
#include "MiscPackets.h"
#include "ObjectMgr.h"
#include "Opcodes.h"
#include "Player.h"
@@ -2059,6 +2060,16 @@ GroupJoinBattlegroundResult Group::CanJoinBattlegroundQueue(Battleground const*
return GroupJoinBattlegroundResult(bgTemplate->GetBgTypeID());
}
void Group::DoMinimapPing(ObjectGuid sourceGuid, float mapX, float mapY)
{
WorldPackets::Misc::MinimapPing minimapPing;
minimapPing.SourceGuid = sourceGuid;
minimapPing.MapX = mapX;
minimapPing.MapY = mapY;
BroadcastPacket(minimapPing.Write(), true, -1, sourceGuid);
}
//===================================================
//============== Roll ===============================
//===================================================
+2
View File
@@ -255,6 +255,8 @@ public:
void SetBattlefieldGroup(Battlefield* bf);
GroupJoinBattlegroundResult CanJoinBattlegroundQueue(Battleground const* bgTemplate, BattlegroundQueueTypeId bgQueueTypeId, uint32 MinPlayerCount, uint32 MaxPlayerCount, bool isRated, uint32 arenaSlot);
void DoMinimapPing(ObjectGuid sourceGuid, float mapX, float mapY);
void ChangeMembersGroup(ObjectGuid guid, uint8 group);
void SetTargetIcon(uint8 id, ObjectGuid whoGuid, ObjectGuid targetGuid);
void SetGroupMemberFlag(ObjectGuid guid, bool apply, GroupMemberFlags flag);
@@ -1175,6 +1175,10 @@ void WorldSession::HandlePlayerLoginToCharInWorld(Player* pCurrChar)
pCurrChar->GetMap()->SendInitTransports(pCurrChar);
pCurrChar->GetMap()->SendInitSelf(pCurrChar);
pCurrChar->GetMap()->SendZoneDynamicInfo(pCurrChar);
// If we are logging into an existing player, simply clear visibility references
// so player will receive a fresh list of new objects on the next vis update.
pCurrChar->GetObjectVisibilityContainer().CleanVisibilityReferences();
pCurrChar->UpdateObjectVisibility(false);
pCurrChar->CleanupChannels();
+8 -16
View File
@@ -22,6 +22,7 @@
#include "LFGMgr.h"
#include "Language.h"
#include "Log.h"
#include "MapMgr.h"
#include "MiscPackets.h"
#include "ObjectMgr.h"
#include "Opcodes.h"
@@ -522,24 +523,17 @@ void WorldSession::HandleLootRoll(WorldPacket& recvData)
}
}
void WorldSession::HandleMinimapPingOpcode(WorldPacket& recvData)
void WorldSession::HandleMinimapPingOpcode(WorldPackets::Misc::MinimapPingClient& packet)
{
if (!GetPlayer()->GetGroup())
if (!sMapMgr->IsValidMapCoord(GetPlayer()->GetMap()->GetId(), packet.MapX, packet.MapY))
return;
float x, y;
recvData >> x;
recvData >> y;
Group* group = GetPlayer()->GetGroup();
/** error handling **/
/********************/
if (!group)
return;
// everything's fine, do it
WorldPacket data(MSG_MINIMAP_PING, (8 + 4 + 4));
data << GetPlayer()->GetGUID();
data << float(x);
data << float(y);
GetPlayer()->GetGroup()->BroadcastPacket(&data, true, -1, GetPlayer()->GetGUID());
group->DoMinimapPing(GetPlayer()->GetGUID(), packet.MapX, packet.MapY);
}
void WorldSession::HandleRandomRollOpcode(WorldPackets::Misc::RandomRollClient& packet)
@@ -549,10 +543,8 @@ void WorldSession::HandleRandomRollOpcode(WorldPackets::Misc::RandomRollClient&
maximum = packet.Max;
/** error handling **/
if (minimum > maximum || maximum > 10000) // < 32768 for urand call
{
if (minimum > maximum || maximum > sWorld->getIntConfig(CONFIG_RANDOM_ROLL_MAXIMUM))
return;
}
GetPlayer()->DoRandomRoll(minimum, maximum);
}
+25 -34
View File
@@ -24,6 +24,7 @@
#include "Opcodes.h"
#include "Pet.h"
#include "Player.h"
#include "QueryPackets.h"
#include "World.h"
#include "WorldPacket.h"
#include "WorldSession.h"
@@ -32,62 +33,55 @@ void WorldSession::SendNameQueryOpcode(ObjectGuid guid)
{
CharacterCacheEntry const* playerData = sCharacterCache->GetCharacterCacheByGuid(guid);
WorldPacket data(SMSG_NAME_QUERY_RESPONSE, (8 + 1 + 1 + 1 + 1 + 1 + 10));
data << guid.WriteAsPacked();
WorldPackets::Query::NameQueryResponse nameQueryResponse;
nameQueryResponse.Guid = guid.WriteAsPacked();
if (!playerData)
{
data << uint8(1); // name unknown
SendPacket(&data);
nameQueryResponse.NameUnknown = true;
SendPacket(nameQueryResponse.Write());
return;
}
Player* player = ObjectAccessor::FindConnectedPlayer(guid);
data << uint8(0); // name known
data << playerData->Name; // played name
data << uint8(0); // realm name - only set for cross realm interaction (such as Battlegrounds)
data << uint8(player ? player->getRace() : playerData->Race);
data << uint8(playerData->Sex);
data << uint8(playerData->Class);
nameQueryResponse.NameUnknown = false;
nameQueryResponse.Name = playerData->Name;
nameQueryResponse.Race = player ? player->getRace() : playerData->Race;
nameQueryResponse.Sex = player ? player->getGender() : playerData->Sex;
nameQueryResponse.Class = player ? player->getClass() : playerData->Class;
// pussywizard: optimization
/*Player* player = ObjectAccessor::FindConnectedPlayer(guid);
if (DeclinedName const* names = (player ? player->GetDeclinedNames() : nullptr))
{
data << uint8(1); // Name is declined
for (uint8 i = 0; i < MAX_DECLINED_NAME_CASES; ++i)
data << names->name[i];
nameQueryResponse.Declined = true;
nameQueryResponse.DeclinedNames = *names;
}
else*/
data << uint8(0); // Name is not declined
else
nameQueryResponse.Declined = false;
SendPacket(&data);
SendPacket(nameQueryResponse.Write());
}
void WorldSession::HandleNameQueryOpcode(WorldPacket& recvData)
void WorldSession::HandleNameQueryOpcode(WorldPackets::Query::NameQuery& packet)
{
ObjectGuid guid;
recvData >> guid;
// This is disable by default to prevent lots of console spam
// LOG_INFO("network.opcode", "HandleNameQueryOpcode {}", guid);
SendNameQueryOpcode(guid);
SendNameQueryOpcode(packet.Guid);
}
void WorldSession::HandleQueryTimeOpcode(WorldPacket& /*recvData*/)
void WorldSession::HandleTimeQueryOpcode(WorldPackets::Query::TimeQuery& /*packet*/)
{
SendQueryTimeResponse();
SendTimeQueryResponse();
}
void WorldSession::SendQueryTimeResponse()
void WorldSession::SendTimeQueryResponse()
{
auto timeResponse = sWorld->GetNextDailyQuestsResetTime() - GameTime::GetGameTime();
WorldPacket data(SMSG_QUERY_TIME_RESPONSE, 4 + 4);
data << uint32(GameTime::GetGameTime().count());
data << uint32(timeResponse.count());
SendPacket(&data);
WorldPackets::Query::TimeQueryResponse timeQueryResponse;
timeQueryResponse.ServerTime = GameTime::GetGameTime().count();
timeQueryResponse.TimeResponse = timeResponse.count();
SendPacket(timeQueryResponse.Write());
}
/// Only _static_ data is sent in this packet !!!
@@ -402,13 +396,10 @@ void WorldSession::HandlePageTextQueryOpcode(WorldPacket& recvData)
}
}
void WorldSession::HandleCorpseMapPositionQuery(WorldPacket& recvData)
void WorldSession::HandleCorpseMapPositionQuery(WorldPackets::Query::CorpseMapPositionQuery& /*packet*/)
{
LOG_DEBUG("network", "WORLD: Recv CMSG_CORPSE_MAP_POSITION_QUERY");
uint32 unk;
recvData >> unk;
WorldPacket data(SMSG_CORPSE_MAP_POSITION_QUERY_RESPONSE, 4 + 4 + 4 + 4);
data << float(0);
data << float(0);
+1 -1
View File
@@ -172,7 +172,7 @@ void WorldSession::HandleGMTicketDeleteOpcode(WorldPacket& /*recv_data*/)
void WorldSession::HandleGMTicketGetTicketOpcode(WorldPacket& /*recv_data*/)
{
SendQueryTimeResponse();
SendTimeQueryResponse();
if (GmTicket* ticket = sTicketMgr->GetTicketByPlayer(GetPlayer()->GetGUID()))
{
+20 -33
View File
@@ -345,7 +345,7 @@ bool Map::AddToMap(T* obj, bool checkTransport)
}
template<>
bool Map::AddToMap(MotionTransport* obj, bool /*checkTransport*/)
bool Map::AddToMap(Transport* obj, bool /*checkTransport*/)
{
//TODO: Needs clean up. An object should not be added to map twice.
if (obj->IsInWorld())
@@ -360,26 +360,22 @@ bool Map::AddToMap(MotionTransport* obj, bool /*checkTransport*/)
}
Cell cell(cellCoord);
if (obj->isActiveObject())
EnsureGridLoaded(cell);
EnsureGridLoaded(cell);
obj->AddToWorld();
_transports.insert(obj);
// Broadcast creation to players
if (!GetPlayers().IsEmpty())
for (Map::PlayerList::const_iterator itr = GetPlayers().begin(); itr != GetPlayers().end(); ++itr)
{
for (Map::PlayerList::const_iterator itr = GetPlayers().begin(); itr != GetPlayers().end(); ++itr)
if (itr->GetSource()->GetTransport() != obj)
{
if (itr->GetSource()->GetTransport() != obj)
{
UpdateData data;
obj->BuildCreateUpdateBlockForPlayer(&data, itr->GetSource());
WorldPacket packet;
data.BuildPacket(packet);
itr->GetSource()->SendDirectMessage(&packet);
}
UpdateData data;
obj->BuildCreateUpdateBlockForPlayer(&data, itr->GetSource());
WorldPacket packet;
data.BuildPacket(packet);
itr->GetSource()->SendDirectMessage(&packet);
}
}
@@ -667,7 +663,7 @@ void Map::RemoveFromMap(T* obj, bool remove)
}
template<>
void Map::RemoveFromMap(MotionTransport* obj, bool remove)
void Map::RemoveFromMap(Transport* obj, bool remove)
{
obj->RemoveFromWorld();
@@ -697,8 +693,6 @@ void Map::RemoveFromMap(MotionTransport* obj, bool remove)
obj->ResetMap();
// Transports are never actually deleted, but it *should* be safe to clear
// from update list when removing from world
RemoveObjectFromMapUpdateList(obj);
if (remove)
@@ -966,11 +960,10 @@ void Map::UnloadAll()
for (TransportsContainer::iterator itr = _transports.begin(); itr != _transports.end();)
{
MotionTransport* transport = *itr;
Transport* transport = *itr;
++itr;
transport->RemoveFromWorld();
delete transport;
RemoveFromMap<Transport>(transport, true);
}
_transports.clear();
@@ -1589,6 +1582,9 @@ void Map::SendInitSelf(Player* player)
void Map::SendInitTransports(Player* player)
{
if (_transports.empty())
return;
// Hack to send out transports
UpdateData transData;
for (TransportsContainer::const_iterator itr = _transports.begin(); itr != _transports.end(); ++itr)
@@ -1605,24 +1601,15 @@ void Map::SendInitTransports(Player* player)
void Map::SendRemoveTransports(Player* player)
{
if (_transports.empty())
return;
// Hack to send out transports
UpdateData transData;
for (TransportsContainer::const_iterator itr = _transports.begin(); itr != _transports.end(); ++itr)
if (*itr != player->GetTransport())
(*itr)->BuildOutOfRangeUpdateBlock(&transData);
// pussywizard: remove static transports from client
/*for (GuidUnorderedSet::const_iterator it = player->m_clientGUIDs.begin(); it != player->m_clientGUIDs.end(); )
{
if ((*it).IsTransport())
{
transData.AddOutOfRangeGUID(*it);
it = player->m_clientGUIDs.erase(it);
}
else
++it;
}*/
if (!transData.HasData())
return;
@@ -1699,7 +1686,7 @@ void Map::DelayedUpdate(const uint32 t_diff)
{
for (_transportsUpdateIter = _transports.begin(); _transportsUpdateIter != _transports.end();)
{
MotionTransport* transport = *_transportsUpdateIter;
Transport* transport = *_transportsUpdateIter;
++_transportsUpdateIter;
if (!transport->IsInWorld())
@@ -1744,7 +1731,7 @@ void Map::RemoveAllObjectsInRemoveList()
RemoveFromMap((DynamicObject*)obj, true);
break;
case TYPEID_GAMEOBJECT:
if (MotionTransport* transport = obj->ToGameObject()->ToMotionTransport())
if (Transport* transport = obj->ToGameObject()->ToTransport())
RemoveFromMap(transport, true);
else
RemoveFromMap(obj->ToGameObject(), true);
+2 -3
View File
@@ -146,7 +146,7 @@ struct ZoneDynamicInfo
typedef std::map<uint32/*leaderDBGUID*/, CreatureGroup*> CreatureGroupHolderType;
typedef std::unordered_map<uint32 /*zoneId*/, ZoneDynamicInfo> ZoneDynamicInfoMap;
typedef std::set<MotionTransport*> TransportsContainer;
typedef std::unordered_set<Transport*> TransportsContainer;
enum EncounterCreditType : uint8
{
@@ -479,7 +479,7 @@ public:
_updateObjects.erase(obj);
}
size_t GetUpdateObjectsCount() const { return _updateObjects.size(); }
size_t GetUpdatableObjectsCount() const { return _updatableObjectList.size(); }
virtual std::string GetDebugInfo() const;
@@ -535,7 +535,6 @@ protected:
MapRefMgr m_mapRefMgr;
MapRefMgr::iterator m_mapRefIter;
// Objects that must update even in inactive grids without activating them
TransportsContainer _transports;
TransportsContainer::iterator _transportsUpdateIter;
+1 -3
View File
@@ -412,10 +412,8 @@ MotionTransport* TransportMgr::CreateTransport(uint32 entry, ObjectGuid::LowType
if (map && map->IsDungeon())
trans->m_zoneScript = map->ToInstanceMap()->GetInstanceScript();
// xinef: transports are active so passengers can be relocated (grids must be loaded)
trans->setActive(true);
HashMapHolder<MotionTransport>::Insert(trans);
trans->GetMap()->AddToMap<MotionTransport>(trans);
trans->GetMap()->AddToMap<Transport>(trans);
return trans;
}
@@ -139,6 +139,11 @@ void ScriptMgr::OnPlayerReputationRankChange(Player* player, uint32 factionID, R
CALL_ENABLED_HOOKS(PlayerScript, PLAYERHOOK_ON_REPUTATION_RANK_CHANGE, script->OnPlayerReputationRankChange(player, factionID, newRank, oldRank, increased));
}
void ScriptMgr::OnPlayerGiveReputation(Player* player, int32 factionID, float& amount, ReputationSource repSource)
{
CALL_ENABLED_HOOKS(PlayerScript, PLAYERHOOK_ON_GIVE_REPUTATION, script->OnPlayerGiveReputation(player, factionID, amount, repSource));
}
void ScriptMgr::OnPlayerLearnSpell(Player* player, uint32 spellID)
{
CALL_ENABLED_HOOKS(PlayerScript, PLAYERHOOK_ON_LEARN_SPELL, script->OnPlayerLearnSpell(player, spellID));
@@ -211,6 +211,7 @@ enum PlayerHook
PLAYERHOOK_CAN_RESURRECT,
PLAYERHOOK_ON_CAN_GIVE_LEVEL,
PLAYERHOOK_ON_SEND_LIST_INVENTORY,
PLAYERHOOK_ON_GIVE_REPUTATION,
PLAYERHOOK_END
};
@@ -285,6 +286,9 @@ public:
// Called when a player's reputation rank changes (before it is actually changed)
virtual void OnPlayerReputationRankChange(Player* /*player*/, uint32 /*factionID*/, ReputationRank /*newRank*/, ReputationRank /*olRank*/, bool /*increased*/) { }
// Called when a player gains Reputation (before anything is given)
virtual void OnPlayerGiveReputation(Player* /*player*/, int32 /*factionID*/, float& /*amount*/, ReputationSource /*repSource*/) { }
// Called when a player learned new spell
virtual void OnPlayerLearnSpell(Player* /*player*/, uint32 /*spellID*/) {}
+1
View File
@@ -334,6 +334,7 @@ public: /* PlayerScript */
void OnPlayerGiveXP(Player* player, uint32& amount, Unit* victim, uint8 xpSource);
bool OnPlayerReputationChange(Player* player, uint32 factionID, int32& standing, bool incremental);
void OnPlayerReputationRankChange(Player* player, uint32 factionID, ReputationRank newRank, ReputationRank oldRank, bool increased);
void OnPlayerGiveReputation(Player* player, int32 factionID, float& amount, ReputationSource repSource);
void OnPlayerLearnSpell(Player* player, uint32 spellID);
void OnPlayerForgotSpell(Player* player, uint32 spellID);
void OnPlayerDuelRequest(Player* target, Player* challenger);
@@ -27,6 +27,7 @@
#include "LFGPackets.h"
#include "MiscPackets.h"
#include "PetPackets.h"
#include "QueryPackets.h"
#include "TotemPackets.h"
#include "WorldStatePackets.h"
@@ -67,6 +67,21 @@ WorldPacket const* WorldPackets::Misc::Playsound::Write()
return &_worldPacket;
}
void WorldPackets::Misc::MinimapPingClient::Read()
{
_worldPacket >> MapX;
_worldPacket >> MapY;
}
WorldPacket const* WorldPackets::Misc::MinimapPing::Write()
{
_worldPacket << SourceGuid;
_worldPacket << float(MapX);
_worldPacket << float(MapY);
return &_worldPacket;
}
void WorldPackets::Misc::RandomRollClient::Read()
{
_worldPacket >> Min;
@@ -93,6 +93,29 @@ namespace WorldPackets
uint32 SoundKitID = 0;
};
class MinimapPingClient final : public ClientPacket
{
public:
MinimapPingClient(WorldPacket&& packet) : ClientPacket(MSG_MINIMAP_PING, std::move(packet)) {}
void Read() override;
float MapX = 0.0f; // Raw position coordinates
float MapY = 0.0f;
};
class MinimapPing final : public ServerPacket
{
public:
MinimapPing() : ServerPacket(MSG_MINIMAP_PING, 8 + 4 + 4) { }
WorldPacket const* Write() override;
ObjectGuid SourceGuid;
float MapX = 0.0f;
float MapY = 0.0f;
};
class RandomRollClient final : public ClientPacket
{
public:
@@ -0,0 +1,58 @@
/*
* 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 "QueryPackets.h"
void WorldPackets::Query::NameQuery::Read()
{
_worldPacket >> Guid;
}
WorldPacket const* WorldPackets::Query::NameQueryResponse::Write()
{
_worldPacket << Guid;
_worldPacket << NameUnknown;
if (NameUnknown)
return &_worldPacket;
_worldPacket << Name;
_worldPacket << RealmName;
_worldPacket << Race;
_worldPacket << Sex;
_worldPacket << Class;
_worldPacket << Declined;
if (Declined)
{
for (uint8 i = 0; i < MAX_DECLINED_NAME_CASES; ++i)
_worldPacket << DeclinedNames.name[i];
}
return &_worldPacket;
}
WorldPacket const* WorldPackets::Query::TimeQueryResponse::Write()
{
_worldPacket << ServerTime;
_worldPacket << TimeResponse;
return &_worldPacket;
}
void WorldPackets::Query::CorpseMapPositionQuery::Read()
{
_worldPacket >> unk;
}
@@ -0,0 +1,87 @@
/*
* 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 QueryPackets_h__
#define QueryPackets_h__
#include "Packet.h"
#include "Unit.h"
namespace WorldPackets
{
namespace Query
{
class NameQuery final : public ClientPacket
{
public:
NameQuery(WorldPacket&& packet) : ClientPacket(CMSG_NAME_QUERY, std::move(packet)) {}
void Read() override;
ObjectGuid Guid;
};
class NameQueryResponse final : public ServerPacket
{
public:
NameQueryResponse() : ServerPacket(SMSG_NAME_QUERY_RESPONSE, 8 + 1 + 1 + 1 + 1 + 1 + 10) {}
WorldPacket const* Write() override;
PackedGuid Guid;
uint8 NameUnknown = false;
std::string_view Name;
std::string_view RealmName = ""; // Only set for cross realm interaction (such as Battlegrounds)
uint8 Race = RACE_NONE;
uint8 Sex = GENDER_MALE;
uint8 Class = CLASS_NONE;
uint8 Declined = false;
DeclinedName DeclinedNames;
};
class TimeQuery final : public ClientPacket
{
public:
TimeQuery(WorldPacket&& packet) : ClientPacket(CMSG_QUERY_TIME, std::move(packet)) {}
void Read() override {};
};
class TimeQueryResponse final : public ServerPacket
{
public:
TimeQueryResponse() : ServerPacket(SMSG_QUERY_TIME_RESPONSE, 4 + 4) {}
WorldPacket const* Write() override;
uint32 ServerTime;
uint32 TimeResponse;
};
class CorpseMapPositionQuery final : public ClientPacket
{
public:
CorpseMapPositionQuery(WorldPacket&& packet) : ClientPacket(CMSG_CORPSE_MAP_POSITION_QUERY, std::move(packet)) {}
void Read() override;
uint32 unk;
};
}
}
#endif // QueryPackets_h__
+1 -1
View File
@@ -590,7 +590,7 @@ void OpcodeTable::Initialize()
/*0x1CB*/ DEFINE_SERVER_OPCODE_HANDLER(SMSG_NOTIFICATION, STATUS_NEVER);
/*0x1CC*/ DEFINE_HANDLER(CMSG_PLAYED_TIME, STATUS_LOGGEDIN, PROCESS_INPLACE, &WorldSession::HandlePlayedTime );
/*0x1CD*/ DEFINE_SERVER_OPCODE_HANDLER(SMSG_PLAYED_TIME, STATUS_NEVER);
/*0x1CE*/ DEFINE_HANDLER(CMSG_QUERY_TIME, STATUS_LOGGEDIN, PROCESS_INPLACE, &WorldSession::HandleQueryTimeOpcode );
/*0x1CE*/ DEFINE_HANDLER(CMSG_QUERY_TIME, STATUS_LOGGEDIN, PROCESS_INPLACE, &WorldSession::HandleTimeQueryOpcode );
/*0x1CF*/ DEFINE_SERVER_OPCODE_HANDLER(SMSG_QUERY_TIME_RESPONSE, STATUS_NEVER);
/*0x1D0*/ DEFINE_SERVER_OPCODE_HANDLER(SMSG_LOG_XPGAIN, STATUS_NEVER);
/*0x1D1*/ DEFINE_SERVER_OPCODE_HANDLER(SMSG_AURACASTLOG, STATUS_NEVER);
+13 -5
View File
@@ -148,6 +148,7 @@ namespace WorldPackets
namespace Misc
{
class MinimapPingClient;
class RandomRollClient;
}
@@ -159,6 +160,13 @@ namespace WorldPackets
class PetSpellAutocast;
class RequestPetInfo;
}
namespace Query
{
class NameQuery;
class TimeQuery;
class CorpseMapPositionQuery;
}
}
enum AccountDataType
@@ -389,7 +397,7 @@ public:
}
void SendSetPhaseShift(uint32 phaseShift);
void SendQueryTimeResponse();
void SendTimeQueryResponse();
void SendAuthResponse(uint8 code, bool shortForm, uint32 queuePos = 0);
void SendClientCacheVersion(uint32 version);
@@ -680,9 +688,9 @@ public: // opcodes handlers
void HandleGameObjectUseOpcode(WorldPacket& recPacket);
void HandleGameobjectReportUse(WorldPacket& recvPacket);
void HandleNameQueryOpcode(WorldPacket& recvPacket);
void HandleNameQueryOpcode(WorldPackets::Query::NameQuery& packet);
void HandleQueryTimeOpcode(WorldPacket& recvPacket);
void HandleTimeQueryOpcode(WorldPackets::Query::TimeQuery& packet);
void HandleCreatureQueryOpcode(WorldPacket& recvPacket);
@@ -882,7 +890,7 @@ public: // opcodes handlers
void HandleReclaimCorpseOpcode(WorldPacket& recvPacket);
void HandleCorpseQueryOpcode(WorldPacket& recvPacket);
void HandleCorpseMapPositionQuery(WorldPacket& recvPacket);
void HandleCorpseMapPositionQuery(WorldPackets::Query::CorpseMapPositionQuery& packet);
void HandleResurrectResponseOpcode(WorldPacket& recvPacket);
void HandleSummonResponseOpcode(WorldPacket& recvData);
@@ -953,7 +961,7 @@ public: // opcodes handlers
void HandleWardenDataOpcode(WorldPacket& recvData);
void HandleWorldTeleportOpcode(WorldPacket& recvData);
void HandleMinimapPingOpcode(WorldPacket& recvData);
void HandleMinimapPingOpcode(WorldPackets::Misc::MinimapPingClient& packet);
void HandleRandomRollOpcode(WorldPackets::Misc::RandomRollClient& packet);
void HandleFarSightOpcode(WorldPacket& recvData);
void HandleSetDungeonDifficultyOpcode(WorldPacket& recvData);
+2
View File
@@ -492,6 +492,8 @@ void WorldConfig::BuildConfigCache()
SetConfigValue<bool>(CONFIG_LEAVE_GROUP_ON_LOGOUT, "LeaveGroupOnLogout.Enabled", false);
SetConfigValue<uint32>(CONFIG_RANDOM_ROLL_MAXIMUM, "Group.RandomRollMaximum", 1000000);
SetConfigValue<bool>(CONFIG_QUEST_POI_ENABLED, "QuestPOI.Enabled", true);
SetConfigValue<uint32>(CONFIG_CHANGE_FACTION_MAX_MONEY, "ChangeFaction.MaxMoney", 0);
+1
View File
@@ -129,6 +129,7 @@ enum ServerConfigs
CONFIG_ALLOW_JOIN_BG_AND_LFG,
CONFIG_MISS_CHANCE_MULTIPLIER_ONLY_FOR_PLAYERS,
CONFIG_LEAVE_GROUP_ON_LOGOUT,
CONFIG_RANDOM_ROLL_MAXIMUM,
CONFIG_QUEST_POI_ENABLED,
CONFIG_VMAP_BLIZZLIKE_PVP_LOS,
CONFIG_VMAP_BLIZZLIKE_LOS_OPEN_WORLD,
+3 -3
View File
@@ -73,7 +73,7 @@ public:
{ "hostile", HandleDebugHostileRefListCommand, SEC_ADMINISTRATOR, Console::No },
{ "anim", HandleDebugAnimCommand, SEC_ADMINISTRATOR, Console::No },
{ "arena", HandleDebugArenaCommand, SEC_ADMINISTRATOR, Console::No },
{ "bg", HandleDebugBattlegroundCommand, SEC_ADMINISTRATOR, Console::No },
{ "bg", HandleDebugBattlegroundCommand, SEC_ADMINISTRATOR, Console::Yes},
{ "cooldown", HandleDebugCooldownCommand, SEC_ADMINISTRATOR, Console::No },
{ "getitemstate", HandleDebugGetItemStateCommand, SEC_ADMINISTRATOR, Console::No },
{ "lootrecipient", HandleDebugGetLootRecipientCommand, SEC_ADMINISTRATOR, Console::No },
@@ -92,7 +92,7 @@ public:
{ "update", HandleDebugUpdateCommand, SEC_ADMINISTRATOR, Console::No },
{ "itemexpire", HandleDebugItemExpireCommand, SEC_ADMINISTRATOR, Console::No },
{ "areatriggers", HandleDebugAreaTriggersCommand, SEC_ADMINISTRATOR, Console::No },
{ "lfg", HandleDebugDungeonFinderCommand, SEC_ADMINISTRATOR, Console::No },
{ "lfg", HandleDebugDungeonFinderCommand, SEC_ADMINISTRATOR, Console::Yes},
{ "los", HandleDebugLoSCommand, SEC_ADMINISTRATOR, Console::No },
{ "moveflags", HandleDebugMoveflagsCommand, SEC_ADMINISTRATOR, Console::No },
{ "unitstate", HandleDebugUnitStateCommand, SEC_ADMINISTRATOR, Console::No },
@@ -1351,7 +1351,7 @@ public:
map->GetId(), map->GetMapName(), map->GetInstanceId(),
uint64(map->GetObjectsStore().Size<Creature>()),
uint64(map->GetObjectsStore().Size<GameObject>()),
uint64(map->GetUpdateObjectsCount()));
uint64(map->GetUpdatableObjectsCount()));
CreatureCountWorker worker;
TypeContainerVisitor<CreatureCountWorker, MapStoredObjectTypesContainer> visitor(worker);
@@ -0,0 +1,375 @@
/*
* This file is part of the AzerothCore Project. See AUTHORS file for Copyright information
*
* This program is free software; you can redistribute it and/or modify it
* under the terms of the GNU Affero General Public License as published by the
* Free Software Foundation; either version 3 of the License, or (at your
* option) any later version.
*
* This program is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for
* more details.
*
* You should have received a copy of the GNU General Public License along
* with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include "karazhan.h"
#include "AreaTriggerScript.h"
#include "CreatureScript.h"
#include "GameObjectAI.h"
#include "GameObjectScript.h"
#include "Player.h"
#include "ScriptedCreature.h"
#include "SpellInfo.h"
#include "SpellScript.h"
#include "SpellScriptLoader.h"
#include "UnitAI.h"
enum Text
{
SAY_APPROACH = 0,
SAY_AGGRO = 1,
SAY_SUMMON = 2
};
enum Spells
{
SPELL_BLOOD_MIRROR0 = 50844,
SPELL_BLOOD_MIRROR1 = 50845,
SPELL_BLOOD_MIRROR_TARGET_PICKER = 50883,
SPELL_BLOOD_MIRROR_TRANSITION_VISUAL = 50910,
SPELL_BLOOD_MIRROR_DAMAGE = 50846,
SPELL_BLOOD_TAP = 51135,
SPELL_BLOOD_SWOOP = 50922,
SPELL_DASH_GASH_PRE_SPELL = 50923,
SPELL_DASH_GASH_RETURN_TO_TANK = 50924,
// SPELL_DASH_GASH_RETURN_TO_TANK_PRE_SPELL = 50925,
// SPELL_DASH_GASH_RETURN_TO_TANK_PRE_SPELL_ROOT = 50932,
SPELL_DESPAWN_SANGUINE_SPIRIT_VISUAL = 51214,
SPELL_DESPAWN_SANGUINE_SPIRITS = 51212,
SPELL_SANGUINE_SPIRIT_AURA = 50993,
SPELL_SANGUINE_SPIRIT_PRE_AURA = 51282,
SPELL_SANGUINE_SPIRIT_PRE_AURA2 = 51283,
SPELL_SUMMON_SANGUINE_SPIRIT0 = 50996,
SPELL_SUMMON_SANGUINE_SPIRIT1 = 50998,
// SPELL_SUMMON_SANGUINE_SPIRIT2 = 51204,
SPELL_SUMMON_SANGUINE_SPIRIT_MISSILE_BURST = 51208,
SPELL_SUMMON_SANGUINE_SPIRIT_SHORT_MISSILE_BURST = 51280,
SPELL_SUMMON_SANGUINE_SPIRIT_ON_KILL = 51205,
SPELL_EXSANGUINATE = 51013,
SPELL_DUMMY_NUKE_RANGE_SELF = 51106,
};
enum Events
{
EVENT_SAY = 1,
EVENT_FLAG = 2
};
struct boss_tenris_mirkblood : public BossAI
{
boss_tenris_mirkblood(Creature* creature) : BossAI(creature, DATA_MIRKBLOOD)
{
scheduler.SetValidator([this]
{
return !me->HasUnitState(UNIT_STATE_CASTING);
});
}
void Reset() override
{
_Reset();
me->SetImmuneToPC(true);
ScheduleHealthCheckEvent(50, [&] {
Talk(SAY_SUMMON);
DoCast(SPELL_SUMMON_SANGUINE_SPIRIT_MISSILE_BURST);
});
ScheduleHealthCheckEvent(45, [&] {
ScheduleTimedEvent(10s, 15s, [&] {
DoCast(SPELL_BLOOD_TAP);
}, 15s, 40s);
});
}
void JustEngagedWith(Unit* /*who*/) override
{
DoZoneInCombat();
ScheduleTimedEvent(1s, 5s, [&] {
// Blood Mirror
DoCast(SPELL_BLOOD_MIRROR_TARGET_PICKER);
}, 20s, 50s);
ScheduleTimedEvent(30s, [&] {
// Blood Swoop
DoCast(SPELL_DASH_GASH_PRE_SPELL);
}, 15s, 40s);
ScheduleTimedEvent(6s, 15s, [&] {
// Sanguine Spirit
DoCast(SPELL_SUMMON_SANGUINE_SPIRIT_SHORT_MISSILE_BURST);
}, 6s, 15s);
}
void KilledUnit(Unit* victim) override
{
if (!victim)
return;
DoCast(victim, SPELL_SUMMON_SANGUINE_SPIRIT_ON_KILL);
}
void DamageTaken(Unit* attacker, uint32& damage, DamageEffectType damageType, SpellSchoolMask damageSchoolMask) override
{
BossAI::DamageTaken(attacker, damage, damageType, damageSchoolMask);
if (!me->HasAura(SPELL_BLOOD_MIRROR0))
return;
if (!_mirrorTarget)
return;
int32 damageTaken = damage;
me->CastCustomSpell(_mirrorTarget, SPELL_BLOOD_MIRROR_DAMAGE, &damageTaken, &damageTaken, &damageTaken, true, nullptr, nullptr, me->GetGUID());
}
void SpellHit(Unit* caster, SpellInfo const* spell) override
{
if (spell->Id == SPELL_BLOOD_MIRROR0 && caster != me)
_mirrorTarget = caster;
}
void EnterEvadeMode(EvadeReason why) override
{
_EnterEvadeMode(why);
me->SetImmuneToPC(false);
}
private:
Unit* _mirrorTarget = nullptr;
};
struct npc_sanguine_spirit : public ScriptedAI
{
npc_sanguine_spirit(Creature* creature) : ScriptedAI(creature) {}
void Reset() override
{
scheduler.CancelAll();
me->ApplySpellImmune(0, IMMUNITY_SCHOOL, SPELL_SCHOOL_MASK_ALL, true);
me->SetReactState(REACT_PASSIVE);
DoCastSelf(SPELL_SANGUINE_SPIRIT_PRE_AURA);
scheduler.Schedule(5s, [this](TaskContext /*context*/)
{
DoCastSelf(SPELL_SANGUINE_SPIRIT_PRE_AURA2);
}).Schedule(3s, [this](TaskContext /*context*/)
{
me->SetReactState(REACT_AGGRESSIVE);
me->SetInCombatWithZone();
DoCastSelf(SPELL_SANGUINE_SPIRIT_AURA);
});
}
void UpdateAI(uint32 diff) override
{
scheduler.Update(diff);
UpdateVictim();
}
};
class spell_mirkblood_blood_mirror_target_picker : public SpellScript
{
PrepareSpellScript(spell_mirkblood_blood_mirror_target_picker)
bool Validate(SpellInfo const* /*spell*/) override
{
return ValidateSpellInfo({ SPELL_BLOOD_MIRROR0, SPELL_BLOOD_MIRROR1, SPELL_BLOOD_MIRROR_TRANSITION_VISUAL });
}
void HandleHit()
{
Unit* caster = GetCaster();
if (!caster->ToCreature())
return;
Unit* target = caster->GetAI()->SelectTarget(SelectTargetMethod::Random, 0, 0.0f, true, false);
if (!target) // Only Blood Mirror the tank if they're the only one around
target = caster->GetVictim();
if (!target)
return;
caster->CastSpell(caster, SPELL_BLOOD_MIRROR_TRANSITION_VISUAL, TRIGGERED_FULL_MASK);
caster->CastSpell(target, SPELL_BLOOD_MIRROR_TRANSITION_VISUAL, TRIGGERED_FULL_MASK);
caster->AddAura(SPELL_BLOOD_MIRROR1, caster); // Should be a cast, but channeled spell results in either Mirkblood or player being unactionable
caster->AddAura(SPELL_BLOOD_MIRROR1, target); // Adding aura manually causes visual to not appear properly, but better than breaking gameplay
target->CastSpell(caster, SPELL_BLOOD_MIRROR0); // Clone player
}
void Register() override
{
OnCast += SpellCastFn(spell_mirkblood_blood_mirror_target_picker::HandleHit);
}
};
class spell_mirkblood_dash_gash_return_to_tank_pre_spell : public SpellScript
{
PrepareSpellScript(spell_mirkblood_dash_gash_return_to_tank_pre_spell)
bool Validate(SpellInfo const* /*spell*/) override
{
return ValidateSpellInfo({ SPELL_DASH_GASH_RETURN_TO_TANK });
}
void HandleCast()
{
if (!GetCaster() || !GetCaster()->GetThreatMgr().GetCurrentVictim())
return;
// Probably wrong, maybe don't charge if would charge the same target?
if (GetCaster()->GetDistance2d(GetCaster()->GetThreatMgr().GetCurrentVictim()) < 5.0f)
return;
GetCaster()->CastSpell(GetCaster()->GetVictim(), SPELL_DASH_GASH_RETURN_TO_TANK);
}
void Register() override
{
OnCast += SpellCastFn(spell_mirkblood_dash_gash_return_to_tank_pre_spell::HandleCast);
}
};
class spell_mirkblood_exsanguinate : public SpellScript
{
PrepareSpellScript(spell_mirkblood_exsanguinate)
void CalculateDamage()
{
if (!GetHitUnit())
return;
SetHitDamage(std::max((GetHitUnit()->GetHealth() * 0.66f), 2000.0f));
}
void Register() override
{
OnHit += SpellHitFn(spell_mirkblood_exsanguinate::CalculateDamage);
}
};
class at_karazhan_mirkblood_approach : public AreaTriggerScript
{
public:
at_karazhan_mirkblood_approach() : AreaTriggerScript("at_karazhan_mirkblood_approach") {}
bool OnTrigger(Player* player, AreaTrigger const* /*trigger*/) override
{
if (InstanceScript* instance = player->GetInstanceScript())
if (instance->GetBossState(DATA_MIRKBLOOD) != DONE)
if (Creature* mirkblood = instance->GetCreature(DATA_MIRKBLOOD))
mirkblood->AI()->Talk(SAY_APPROACH, player);
return false;
}
};
class at_karazhan_mirkblood_entrance : public AreaTriggerScript
{
public:
at_karazhan_mirkblood_entrance() : AreaTriggerScript("at_karazhan_mirkblood_entrance") {}
bool OnTrigger(Player* player, AreaTrigger const* /*trigger*/) override
{
if (InstanceScript* instance = player->GetInstanceScript())
if (instance->GetBossState(DATA_MIRKBLOOD) != DONE)
if (Creature* mirkblood = instance->GetCreature(DATA_MIRKBLOOD))
mirkblood->SetImmuneToPC(false);
return false;
}
};
class go_blood_drenched_door : public GameObjectScript
{
public:
go_blood_drenched_door() : GameObjectScript("go_blood_drenched_door") {}
struct go_blood_drenched_doorAI : public GameObjectAI
{
go_blood_drenched_doorAI(GameObject* go) : GameObjectAI(go) {}
EventMap events;
Creature* mirkblood;
Player* opener;
bool GossipHello(Player* player, bool /*reportUse*/) override
{
events.Reset();
if (InstanceScript* instance = player->GetInstanceScript())
if (instance->GetBossState(DATA_MIRKBLOOD) != DONE)
{
opener = player;
mirkblood = instance->GetCreature(DATA_MIRKBLOOD);
events.ScheduleEvent(EVENT_SAY, 1s);
events.ScheduleEvent(EVENT_FLAG, 5s);
me->SetGameObjectFlag(GO_FLAG_NOT_SELECTABLE);
}
return true;
}
void UpdateAI(uint32 diff) override
{
if (events.Empty())
return;
events.Update(diff);
switch (events.ExecuteEvent())
{
case EVENT_SAY:
if (!mirkblood)
return;
mirkblood->AI()->Talk(SAY_AGGRO, opener);
break;
case EVENT_FLAG:
if (!mirkblood)
return;
mirkblood->SetImmuneToPC(false);
me->Delete();
break;
}
}
};
GameObjectAI* GetAI(GameObject* go) const override
{
return new go_blood_drenched_doorAI(go);
}
};
void AddSC_boss_tenris_mirkblood()
{
RegisterKarazhanCreatureAI(boss_tenris_mirkblood);
RegisterKarazhanCreatureAI(npc_sanguine_spirit);
RegisterSpellScript(spell_mirkblood_blood_mirror_target_picker);
RegisterSpellScript(spell_mirkblood_dash_gash_return_to_tank_pre_spell);
RegisterSpellScript(spell_mirkblood_exsanguinate);
new at_karazhan_mirkblood_approach();
new at_karazhan_mirkblood_entrance();
new go_blood_drenched_door();
}
@@ -47,6 +47,7 @@ ObjectData const creatureData[] =
{ NPC_JULIANNE, DATA_JULIANNE },
{ NPC_NIGHTBANE, DATA_NIGHTBANE },
{ NPC_TERESTIAN_ILLHOOF, DATA_TERESTIAN },
{ NPC_TENRIS_MIRKBLOOD, DATA_MIRKBLOOD },
{ 0, 0 }
};
@@ -73,7 +73,9 @@ enum KZDataTypes
DATA_ROAR = 41,
DATA_STRAWMAN = 42,
DATA_TINHEAD = 43,
DATA_TITO = 44
DATA_TITO = 44,
DATA_MIRKBLOOD = 45
};
enum KZOperaEvents
@@ -135,7 +137,9 @@ enum KZCreatures
// Malchezaar Helpers
NPC_INFERNAL_TARGET = 17644,
NPC_INFERNAL_RELAY = 17645
NPC_INFERNAL_RELAY = 17645,
NPC_TENRIS_MIRKBLOOD = 28194
};
@@ -1181,6 +1181,26 @@ class spell_kiljaeden_dragon_breath : public SpellScript
}
};
// 45848 - Shield of the Blue
class spell_kiljaeden_shield_of_the_blue : public AuraScript
{
PrepareAuraScript(spell_kiljaeden_shield_of_the_blue);
void HandleEffectApply(AuraEffect const* /*aurEff*/, AuraEffectHandleModes /*mode*/)
{
if (Unit* target = GetTarget())
{
target->RemoveAurasDueToSpell(SPELL_FIRE_BLOOM);
target->RemoveMovementImpairingAuras(false);
}
}
void Register() override
{
OnEffectApply += AuraEffectApplyFn(spell_kiljaeden_shield_of_the_blue::HandleEffectApply, EFFECT_0, SPELL_AURA_MOD_DAMAGE_PERCENT_TAKEN, AURA_EFFECT_HANDLE_REAL);
}
};
void AddSC_boss_kiljaeden()
{
RegisterSunwellPlateauCreatureAI(npc_kiljaeden_controller);
@@ -1196,4 +1216,5 @@ void AddSC_boss_kiljaeden()
RegisterSpellScript(spell_kiljaeden_armageddon_periodic_aura);
RegisterSpellScript(spell_kiljaeden_armageddon_missile);
RegisterSpellScript(spell_kiljaeden_dragon_breath);
RegisterSpellScript(spell_kiljaeden_shield_of_the_blue);
}
@@ -78,6 +78,7 @@ void AddSC_bosses_opera();
void AddSC_boss_netherspite();
void AddSC_karazhan();
void AddSC_boss_nightbane();
void AddSC_boss_tenris_mirkblood();
void AddSC_boss_felblood_kaelthas(); // Magister's Terrace
void AddSC_boss_selin_fireheart();
void AddSC_boss_vexallus();
@@ -229,6 +230,7 @@ void AddEasternKingdomsScripts()
AddSC_boss_netherspite();
AddSC_karazhan();
AddSC_boss_nightbane();
AddSC_boss_tenris_mirkblood();
AddSC_boss_felblood_kaelthas(); // Magister's Terrace
AddSC_boss_selin_fireheart();
AddSC_boss_vexallus();
+36
View File
@@ -245,6 +245,20 @@ struct npc_pet_dk_ghoul : public CombatAI
{
npc_pet_dk_ghoul(Creature* c) : CombatAI(c) { }
void IsSummonedBy(WorldObject* summoner) override
{
if (!summoner || !summoner->IsPlayer())
return;
Player* player = summoner->ToPlayer();
if (Unit* victim = player->GetVictim())
{
me->Attack(victim, true);
me->GetMotionMaster()->MoveChase(victim);
}
}
void JustDied(Unit* /*who*/) override
{
if (me->IsGuardian() || me->IsSummon())
@@ -278,6 +292,28 @@ struct npc_pet_dk_army_of_the_dead : public CombatAI
CombatAI::InitializeAI();
((Minion*)me)->SetFollowAngle(rand_norm() * 2 * M_PI);
}
void IsSummonedBy(WorldObject* summoner) override
{
if (Unit* owner = summoner->ToUnit())
{
Unit* victim = owner->GetVictim();
if (victim && me->IsValidAttackTarget(victim))
{
AttackStart(victim);
}
else
{
// If there is no valid target, attack the nearest enemy within 30m
if (Unit* nearest = me->SelectNearbyTarget(nullptr, 30.0f))
{
if (me->IsValidAttackTarget(nearest))
AttackStart(nearest);
}
}
}
}
};
struct npc_pet_dk_dancing_rune_weapon : public NullCreatureAI
+1 -1
View File
@@ -345,7 +345,7 @@ class spell_dk_death_and_decay_aura : public AuraScript
if (GetCaster() && GetTarget())
{
int32 basePoints0 = aurEff->GetAmount();
GetCaster()->CastCustomSpell(GetTarget(), SPELL_DK_DEATH_AND_DECAY_TRIGGER, &basePoints0, nullptr, nullptr, false, 0, aurEff);
GetCaster()->CastCustomSpell(GetTarget(), SPELL_DK_DEATH_AND_DECAY_TRIGGER, &basePoints0, nullptr, nullptr, true, 0, aurEff);
}
}
+11
View File
@@ -187,6 +187,17 @@ enum ReputationRank : uint8
REP_EXALTED = 7
};
enum ReputationSource
{
REPUTATION_SOURCE_KILL,
REPUTATION_SOURCE_QUEST,
REPUTATION_SOURCE_DAILY_QUEST,
REPUTATION_SOURCE_WEEKLY_QUEST,
REPUTATION_SOURCE_MONTHLY_QUEST,
REPUTATION_SOURCE_REPEATABLE_QUEST,
REPUTATION_SOURCE_SPELL
};
enum FactionTemplates
{
FACTION_NONE = 0,