/* * 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 . */ #include "CharmInfo.h" #include "Creature.h" #include "GameTime.h" #include "Map.h" #include "SpellInfo.h" #include "Player.h" #include "SpellMgr.h" #include "StringConvert.h" #include "Tokenize.h" #include "Unit.h" CharmInfo::CharmInfo(Unit* unit) : _unit(unit), _CommandState(COMMAND_FOLLOW), _petnumber(0), _oldReactState(REACT_PASSIVE), _isCommandAttack(false), _isCommandFollow(false), _isAtStay(false), _isFollowing(false), _isReturning(false), _forcedSpellId(0), _stayX(0.0f), _stayY(0.0f), _stayZ(0.0f) { for (uint8 i = 0; i < MAX_SPELL_CHARM; ++i) _charmspells[i].SetActionAndType(0, ACT_DISABLED); if (_unit->IsCreature()) { _oldReactState = _unit->ToCreature()->GetReactState(); _unit->ToCreature()->SetReactState(REACT_PASSIVE); } } CharmInfo::~CharmInfo() = default; void CharmInfo::RestoreState() { if (Creature* creature = _unit->ToCreature()) creature->SetReactState(_oldReactState); } void CharmInfo::InitPetActionBar() { // the first 3 SpellOrActions are attack, follow and stay for (uint32 i = 0; i < ACTION_BAR_INDEX_PET_SPELL_START - ACTION_BAR_INDEX_START; ++i) SetActionBar(ACTION_BAR_INDEX_START + i, COMMAND_ATTACK - i, ACT_COMMAND); // middle 4 SpellOrActions are spells/special attacks/abilities for (uint32 i = 0; i < ACTION_BAR_INDEX_PET_SPELL_END - ACTION_BAR_INDEX_PET_SPELL_START; ++i) SetActionBar(ACTION_BAR_INDEX_PET_SPELL_START + i, 0, ACT_PASSIVE); // last 3 SpellOrActions are reactions for (uint32 i = 0; i < ACTION_BAR_INDEX_END - ACTION_BAR_INDEX_PET_SPELL_END; ++i) SetActionBar(ACTION_BAR_INDEX_PET_SPELL_END + i, COMMAND_ATTACK - i, ACT_REACTION); } void CharmInfo::InitEmptyActionBar(bool withAttack) { if (withAttack) SetActionBar(ACTION_BAR_INDEX_START, COMMAND_ATTACK, ACT_COMMAND); else SetActionBar(ACTION_BAR_INDEX_START, 0, ACT_PASSIVE); for (uint32 x = ACTION_BAR_INDEX_START + 1; x < ACTION_BAR_INDEX_END; ++x) SetActionBar(x, 0, ACT_PASSIVE); } void CharmInfo::InitPossessCreateSpells() { if (_unit->IsCreature()) { InitEmptyActionBar(false); for (uint32 i = 0; i < MAX_CREATURE_SPELLS; ++i) { uint32 spellId = _unit->ToCreature()->m_spells[i]; SpellInfo const* spellInfo = sSpellMgr->GetSpellInfo(spellId); if (spellInfo) if (spellInfo->IsPassive()) _unit->CastSpell(_unit, spellInfo, true); AddSpellToActionBar(spellInfo, ACT_PASSIVE, i); } } else InitEmptyActionBar(); } void CharmInfo::InitCharmCreateSpells() { InitPetActionBar(); if (_unit->IsPlayer()) // charmed players don't have spells return; for (uint32 i = 0; i < MAX_SPELL_CHARM; ++i) { uint32 spellId = _unit->ToCreature()->m_spells[i]; SpellInfo const* spellInfo = sSpellMgr->GetSpellInfo(spellId); if (!spellInfo) { _charmspells[i].SetActionAndType(spellId, ACT_DISABLED); continue; } if (spellInfo->IsPassive()) { _unit->CastSpell(_unit, spellInfo, true); _charmspells[i].SetActionAndType(spellId, ACT_PASSIVE); } else { _charmspells[i].SetActionAndType(spellId, ACT_DISABLED); ActiveStates newstate = ACT_PASSIVE; if (!spellInfo->IsAutocastable()) newstate = ACT_PASSIVE; else { if (spellInfo->NeedsExplicitUnitTarget()) { newstate = ACT_ENABLED; ToggleCreatureAutocast(spellInfo, true); } else newstate = ACT_DISABLED; } AddSpellToActionBar(spellInfo, newstate); } } } bool CharmInfo::AddSpellToActionBar(SpellInfo const* spellInfo, ActiveStates newstate, uint32 index) { uint32 spell_id = 0; uint32 first_id = 0; bool autocastable = false; if (spellInfo) { spell_id = spellInfo->Id; first_id = spellInfo->GetFirstRankSpell()->Id; if (spellInfo->IsAutocastable()) autocastable = true; } // new spell rank can be already listed for (uint8 i = 0; i < MAX_UNIT_ACTION_BAR_INDEX; ++i) { if (uint32 action = PetActionBar[i].GetAction()) { if (PetActionBar[i].IsActionBarForSpell() && sSpellMgr->GetFirstSpellInChain(action) == first_id) { PetActionBar[i].SetAction(spell_id); return true; } } } // or use empty slot in other case for (uint8 i = 0; i < MAX_UNIT_ACTION_BAR_INDEX; ++i) { if (!PetActionBar[i].GetAction() && PetActionBar[i].IsActionBarForSpell()) { if (i != index && index <= MAX_UNIT_ACTION_BAR_INDEX) continue; if (!spell_id && index == ACTION_BAR_INDEX_START) SetActionBar(ACTION_BAR_INDEX_START, COMMAND_ATTACK, ACT_COMMAND); else SetActionBar(i, spell_id, newstate == ACT_DECIDE ? autocastable ? ACT_DISABLED : ACT_PASSIVE : newstate); if (_unit->GetCharmer() && _unit->GetCharmer()->IsPlayer()) { if (Creature* creature = _unit->ToCreature()) { // Processing this packet needs to be delayed _unit->m_Events.AddEventAtOffset([creature, spell_id]() { if (uint32 cooldown = creature->GetSpellCooldown(spell_id)) { WorldPacket data; creature->BuildCooldownPacket(data, SPELL_COOLDOWN_FLAG_NONE, spell_id, cooldown); if (creature->GetCharmer() && creature->GetCharmer()->IsPlayer()) { creature->GetCharmer()->ToPlayer()->SendDirectMessage(&data); } } }, 500ms); } } return true; } } return false; } bool CharmInfo::RemoveSpellFromActionBar(uint32 spell_id) { uint32 first_id = sSpellMgr->GetFirstSpellInChain(spell_id); for (uint8 i = 0; i < MAX_UNIT_ACTION_BAR_INDEX; ++i) { if (uint32 action = PetActionBar[i].GetAction()) { if (PetActionBar[i].IsActionBarForSpell() && sSpellMgr->GetFirstSpellInChain(action) == first_id) { SetActionBar(i, 0, ACT_DISABLED); return true; } } } return false; } void CharmInfo::ToggleCreatureAutocast(SpellInfo const* spellInfo, bool apply) { if (spellInfo->IsPassive()) return; for (uint32 i = 0; i < MAX_SPELL_CHARM; ++i) if (spellInfo->Id == _charmspells[i].GetAction()) _charmspells[i].SetType(apply ? ACT_ENABLED : ACT_DISABLED); } void CharmInfo::SetPetNumber(uint32 petnumber, bool statwindow) { _petnumber = petnumber; if (statwindow) _unit->SetUInt32Value(UNIT_FIELD_PETNUMBER, _petnumber); else _unit->SetUInt32Value(UNIT_FIELD_PETNUMBER, 0); } void CharmInfo::LoadPetActionBar(const std::string& data) { std::vector tokens = Acore::Tokenize(data, ' ', false); if (tokens.size() != (ACTION_BAR_INDEX_END - ACTION_BAR_INDEX_START) * 2) return; // non critical, will reset to default auto iter = tokens.begin(); for (uint8 index = ACTION_BAR_INDEX_START; index < ACTION_BAR_INDEX_END; ++index) { Optional type = Acore::StringTo(*(iter++)); Optional action = Acore::StringTo(*(iter++)); if (!type || !action) { continue; } PetActionBar[index].SetActionAndType(*action, static_cast(*type)); // check correctness if (PetActionBar[index].IsActionBarForSpell()) { SpellInfo const* spellInfo = sSpellMgr->GetSpellInfo(PetActionBar[index].GetAction()); if (!spellInfo) { SetActionBar(index, 0, ACT_PASSIVE); } else if (!spellInfo->IsAutocastable()) { SetActionBar(index, PetActionBar[index].GetAction(), ACT_PASSIVE); } } } } void CharmInfo::BuildActionBar(WorldPacket* data) { for (uint32 i = 0; i < MAX_UNIT_ACTION_BAR_INDEX; ++i) *data << uint32(PetActionBar[i].packedData); } void CharmInfo::SetSpellAutocast(SpellInfo const* spellInfo, bool state) { for (uint8 i = 0; i < MAX_UNIT_ACTION_BAR_INDEX; ++i) { if (spellInfo->Id == PetActionBar[i].GetAction() && PetActionBar[i].IsActionBarForSpell()) { PetActionBar[i].SetType(state ? ACT_ENABLED : ACT_DISABLED); break; } } } void CharmInfo::SetIsCommandAttack(bool val) { _isCommandAttack = val; } bool CharmInfo::IsCommandAttack() { return _isCommandAttack; } void CharmInfo::SetIsCommandFollow(bool val) { _isCommandFollow = val; } bool CharmInfo::IsCommandFollow() { return _isCommandFollow; } void CharmInfo::SaveStayPosition(bool atCurrentPos) { //! At this point a new spline destination is enabled because of Unit::StopMoving() G3D::Vector3 stayPos = G3D::Vector3(); if (atCurrentPos) { float z = INVALID_HEIGHT; _unit->UpdateAllowedPositionZ(_unit->GetPositionX(), _unit->GetPositionY(), z); stayPos = G3D::Vector3(_unit->GetPositionX(), _unit->GetPositionY(), z != INVALID_HEIGHT ? z : _unit->GetPositionZ()); } else stayPos = _unit->movespline->FinalDestination(); if (_unit->movespline->onTransport) if (TransportBase* transport = _unit->GetDirectTransport()) transport->CalculatePassengerPosition(stayPos.x, stayPos.y, stayPos.z); _stayX = stayPos.x; _stayY = stayPos.y; _stayZ = stayPos.z; } void CharmInfo::GetStayPosition(float& x, float& y, float& z) { x = _stayX; y = _stayY; z = _stayZ; } void CharmInfo::RemoveStayPosition() { _stayX = 0.0f; _stayY = 0.0f; _stayZ = 0.0f; } bool CharmInfo::HasStayPosition() { return _stayX && _stayY && _stayZ; } void CharmInfo::SetIsAtStay(bool val) { _isAtStay = val; } bool CharmInfo::IsAtStay() { return _isAtStay; } void CharmInfo::SetIsFollowing(bool val) { _isFollowing = val; } bool CharmInfo::IsFollowing() { return _isFollowing; } void CharmInfo::SetIsReturning(bool val) { _isReturning = val; } bool CharmInfo::IsReturning() { return _isReturning; } //////////////////////////////////////////////////////////// // Methods of class GlobalCooldownMgr bool GlobalCooldownMgr::HasGlobalCooldown(SpellInfo const* spellInfo) const { GlobalCooldownList::const_iterator itr = m_GlobalCooldowns.find(spellInfo->StartRecoveryCategory); return itr != m_GlobalCooldowns.end() && itr->second.duration && getMSTimeDiff(itr->second.cast_time, GameTime::GetGameTimeMS().count()) < itr->second.duration; } uint32 GlobalCooldownMgr::GetGlobalCooldown(SpellInfo const* spellInfo) const { if (!spellInfo) return 0; auto itr = m_GlobalCooldowns.find(spellInfo->StartRecoveryCategory); if (itr == m_GlobalCooldowns.end() || itr->second.duration == 0) return 0; uint32 start = itr->second.cast_time; uint32 delay = itr->second.duration; uint32 now = getMSTime(); return (start + delay > now) ? (start + delay) - now : 0; } void GlobalCooldownMgr::AddGlobalCooldown(SpellInfo const* spellInfo, uint32 gcd) { m_GlobalCooldowns[spellInfo->StartRecoveryCategory] = GlobalCooldown(gcd, GameTime::GetGameTimeMS().count()); } void GlobalCooldownMgr::CancelGlobalCooldown(SpellInfo const* spellInfo) { m_GlobalCooldowns[spellInfo->StartRecoveryCategory].duration = 0; }