I lost track of what was added this time
This commit is contained in:
1 parent
1785350447
commit
e96d617284
47 files changed
+90330
-7063
No files matched your search
@@ -1,79 +0,0 @@
|
||||
# This is the CMakeCache file.
|
||||
# For build in directory: /home/wow_admin/wow_cata_server/source/TrinityCore
|
||||
# It was generated by CMake: /usr/bin/cmake
|
||||
# You can edit this file to change values found and used by cmake.
|
||||
# If you do not want to change any of the values, simply exit the editor.
|
||||
# If you do want to change a value, simply edit, save, and exit the editor.
|
||||
# The syntax for the file is as follows:
|
||||
# KEY:TYPE=VALUE
|
||||
# KEY is the name of a variable in the cache.
|
||||
# TYPE is a hint to GUIs for the type of VALUE, DO NOT EDIT TYPE!.
|
||||
# VALUE is the current value for the KEY.
|
||||
|
||||
########################
|
||||
# EXTERNAL cache entries
|
||||
########################
|
||||
|
||||
//Value Computed by CMake.
|
||||
CMAKE_FIND_PACKAGE_REDIRECTS_DIR:STATIC=/home/wow_admin/wow_cata_server/source/TrinityCore/CMakeFiles/pkgRedirects
|
||||
|
||||
//Value Computed by CMake
|
||||
CMAKE_PROJECT_DESCRIPTION:STATIC=
|
||||
|
||||
//Value Computed by CMake
|
||||
CMAKE_PROJECT_HOMEPAGE_URL:STATIC=
|
||||
|
||||
//Value Computed by CMake
|
||||
CMAKE_PROJECT_NAME:STATIC=TrinityCore
|
||||
|
||||
//Path to a program.
|
||||
CMAKE_UNAME:FILEPATH=/usr/bin/uname
|
||||
|
||||
//Value Computed by CMake
|
||||
TrinityCore_BINARY_DIR:STATIC=/home/wow_admin/wow_cata_server/source/TrinityCore
|
||||
|
||||
//Value Computed by CMake
|
||||
TrinityCore_IS_TOP_LEVEL:STATIC=ON
|
||||
|
||||
//Value Computed by CMake
|
||||
TrinityCore_SOURCE_DIR:STATIC=/home/wow_admin/wow_cata_server/source/TrinityCore
|
||||
|
||||
|
||||
########################
|
||||
# INTERNAL cache entries
|
||||
########################
|
||||
|
||||
//This is the directory where this CMakeCache.txt was created
|
||||
CMAKE_CACHEFILE_DIR:INTERNAL=/home/wow_admin/wow_cata_server/source/TrinityCore
|
||||
//Major version of cmake used to create the current loaded cache
|
||||
CMAKE_CACHE_MAJOR_VERSION:INTERNAL=3
|
||||
//Minor version of cmake used to create the current loaded cache
|
||||
CMAKE_CACHE_MINOR_VERSION:INTERNAL=28
|
||||
//Patch version of cmake used to create the current loaded cache
|
||||
CMAKE_CACHE_PATCH_VERSION:INTERNAL=3
|
||||
//Path to CMake executable.
|
||||
CMAKE_COMMAND:INTERNAL=/usr/bin/cmake
|
||||
//Path to cpack program executable.
|
||||
CMAKE_CPACK_COMMAND:INTERNAL=/usr/bin/cpack
|
||||
//Path to ctest program executable.
|
||||
CMAKE_CTEST_COMMAND:INTERNAL=/usr/bin/ctest
|
||||
//Name of external makefile project generator.
|
||||
CMAKE_EXTRA_GENERATOR:INTERNAL=
|
||||
//Name of generator.
|
||||
CMAKE_GENERATOR:INTERNAL=Unix Makefiles
|
||||
//Generator instance identifier.
|
||||
CMAKE_GENERATOR_INSTANCE:INTERNAL=
|
||||
//Name of generator platform.
|
||||
CMAKE_GENERATOR_PLATFORM:INTERNAL=
|
||||
//Name of generator toolset.
|
||||
CMAKE_GENERATOR_TOOLSET:INTERNAL=
|
||||
//Source directory with the top level CMakeLists.txt file for this
|
||||
// project
|
||||
CMAKE_HOME_DIRECTORY:INTERNAL=/home/wow_admin/wow_cata_server/source/TrinityCore
|
||||
//number of local generators
|
||||
CMAKE_NUMBER_OF_MAKEFILES:INTERNAL=1
|
||||
//Platform information initialized
|
||||
CMAKE_PLATFORM_INFO_INITIALIZED:INTERNAL=1
|
||||
//Path to CMake installation.
|
||||
CMAKE_ROOT:INTERNAL=/usr/share/cmake-3.28
|
||||
|
||||
@@ -1 +0,0 @@
|
||||
# This file is generated by cmake for dependency checking of the CMakeCache.txt file
|
||||
+28133
File diff suppressed because it is too large.
Load diff
@@ -1694,6 +1694,37 @@ class TC_GAME_API Player : public Unit, public GridObject<Player>
|
||||
void UninviteFromGroup();
|
||||
static void RemoveFromGroup(Group* group, ObjectGuid guid, RemoveMethod method = GROUP_REMOVEMETHOD_DEFAULT, ObjectGuid kicker = ObjectGuid::Empty, char const* reason = nullptr);
|
||||
void RemoveFromGroup(RemoveMethod method = GROUP_REMOVEMETHOD_DEFAULT) { RemoveFromGroup(GetGroup(), GetGUID(), method); }
|
||||
|
||||
// VIANO_VIRTUAL_TARGET_ICONS_V1
|
||||
void SetVianoTargetIcon(uint8 id, ObjectGuid guid)
|
||||
{
|
||||
if (id >= _vianoTargetIcons.size())
|
||||
return;
|
||||
|
||||
if (guid)
|
||||
{
|
||||
for (ObjectGuid& existing : _vianoTargetIcons)
|
||||
if (existing == guid)
|
||||
existing.Clear();
|
||||
}
|
||||
|
||||
_vianoTargetIcons[id] = guid;
|
||||
}
|
||||
|
||||
ObjectGuid GetVianoTargetIcon(uint8 id) const
|
||||
{
|
||||
return id < _vianoTargetIcons.size() ? _vianoTargetIcons[id] : ObjectGuid::Empty;
|
||||
}
|
||||
|
||||
void ClearVianoTargetIcon(ObjectGuid guid)
|
||||
{
|
||||
if (!guid)
|
||||
return;
|
||||
|
||||
for (ObjectGuid& existing : _vianoTargetIcons)
|
||||
if (existing == guid)
|
||||
existing.Clear();
|
||||
}
|
||||
void SendUpdateToOutOfRangeGroupMembers();
|
||||
|
||||
void SetInGuild(uint32 guildId);
|
||||
@@ -2609,6 +2640,9 @@ class TC_GAME_API Player : public Unit, public GridObject<Player>
|
||||
|
||||
JoinedChannelsList m_channels;
|
||||
|
||||
// VIANO_VIRTUAL_TARGET_ICONS_V1
|
||||
std::array<ObjectGuid, 8> _vianoTargetIcons{};
|
||||
|
||||
uint8 m_cinematic;
|
||||
uint32 m_movie;
|
||||
|
||||
|
||||
+2776
File diff suppressed because it is too large.
Load diff
File diff suppressed because it is too large.
Load diff
@@ -3121,6 +3121,11 @@ void Unit::_AddAura(UnitAura* aura, Unit* caster)
|
||||
// aura application effects are handled separately to prevent aura list corruption
|
||||
AuraApplication* Unit::_CreateAuraApplication(Aura* aura, uint8 effMask)
|
||||
{
|
||||
// VIANO_AURA_DEBUG_APPLICATION_V1
|
||||
if (aura && aura->GetId() == 465 && IsVianoAdventurer())
|
||||
TC_LOG_INFO("scripts", "[VianoAuraDebug] stage=CreateAuraApplication spell=%u target=%s",
|
||||
aura->GetId(), GetGUID().ToString().c_str());
|
||||
|
||||
// can't apply aura on unit which is going to be deleted - to not create a memory leak
|
||||
ASSERT(!m_cleanupDone);
|
||||
|
||||
@@ -7964,6 +7969,10 @@ void Unit::UpdateMountCapability()
|
||||
AuraEffectList mounts = GetAuraEffectsByType(SPELL_AURA_MOUNTED);
|
||||
for (AuraEffect* aurEff : mounts)
|
||||
{
|
||||
// Quest/event mounts without a MountType do not use MountCapability.
|
||||
if (!aurEff->GetMiscValueB())
|
||||
continue;
|
||||
|
||||
aurEff->RecalculateAmount();
|
||||
if (!aurEff->GetAmount())
|
||||
aurEff->GetBase()->Remove();
|
||||
|
||||
File diff suppressed because it is too large.
Load diff
File diff suppressed because it is too large.
Load diff
@@ -16,6 +16,8 @@
|
||||
*/
|
||||
|
||||
#include "WorldSession.h"
|
||||
|
||||
#include <cstdlib>
|
||||
#include "AccountMgr.h"
|
||||
#include "CellImpl.h"
|
||||
#include "Channel.h"
|
||||
@@ -647,6 +649,67 @@ void WorldSession::HandleAddonMessagechatOpcode(WorldPacket& recvData)
|
||||
return;
|
||||
}
|
||||
|
||||
// VIANO_MOD_BRIDGE_V1
|
||||
// Generic Viano client -> server bridge. First module: virtual raid markers.
|
||||
if (prefix == "VIANO" && type == CHAT_MSG_WHISPER)
|
||||
{
|
||||
auto parseRawGuid = [](std::string const& value, ObjectGuid& guid) -> bool
|
||||
{
|
||||
if (value.empty())
|
||||
return false;
|
||||
char* end = nullptr;
|
||||
uint64 raw = std::strtoull(value.c_str(), &end, 0);
|
||||
if (!end || *end != '\0' || raw == 0)
|
||||
return false;
|
||||
guid.Set(raw);
|
||||
return true;
|
||||
};
|
||||
|
||||
std::vector<std::string> fields;
|
||||
std::size_t begin = 0;
|
||||
while (begin <= message.size())
|
||||
{
|
||||
std::size_t split = message.find('|', begin);
|
||||
if (split == std::string::npos)
|
||||
{
|
||||
fields.push_back(message.substr(begin));
|
||||
break;
|
||||
}
|
||||
fields.push_back(message.substr(begin, split - begin));
|
||||
begin = split + 1;
|
||||
}
|
||||
|
||||
if (fields.size() == 4 && fields[0] == "RM" && fields[1] == "1")
|
||||
{
|
||||
char* end = nullptr;
|
||||
unsigned long iconValue = std::strtoul(fields[2].c_str(), &end, 10);
|
||||
ObjectGuid guid;
|
||||
if (end && *end == '\0' && iconValue < 8 && parseRawGuid(fields[3], guid))
|
||||
{
|
||||
Unit* target = ObjectAccessor::GetUnit(*sender, guid);
|
||||
if (target && target->IsInMap(sender))
|
||||
{
|
||||
sender->SetVianoTargetIcon(uint8(iconValue), guid);
|
||||
TC_LOG_DEBUG("scripts", "[VianoMod] marker player=%s icon=%lu target=%s",
|
||||
sender->GetName().c_str(), iconValue, guid.ToString().c_str());
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (fields.size() == 3 && fields[0] == "RC" && fields[1] == "1")
|
||||
{
|
||||
ObjectGuid guid;
|
||||
if (parseRawGuid(fields[2], guid))
|
||||
sender->ClearVianoTargetIcon(guid);
|
||||
return;
|
||||
}
|
||||
|
||||
TC_LOG_DEBUG("scripts", "[VianoMod] malformed message player=%s payload=%s",
|
||||
sender->GetName().c_str(), message.c_str());
|
||||
return;
|
||||
}
|
||||
|
||||
// Disabled addon channel?
|
||||
if (!sWorld->getBoolConfig(CONFIG_ADDON_CHANNEL))
|
||||
{
|
||||
|
||||
@@ -0,0 +1,860 @@
|
||||
/*
|
||||
* This file is part of the TrinityCore 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 "WorldSession.h"
|
||||
#include "AccountMgr.h"
|
||||
#include "CellImpl.h"
|
||||
#include "Channel.h"
|
||||
#include "ChannelMgr.h"
|
||||
#include "Chat.h"
|
||||
#include "ChatPackets.h"
|
||||
#include "Common.h"
|
||||
#include "DatabaseEnv.h"
|
||||
#include "DBCStores.h"
|
||||
#include "GameTime.h"
|
||||
#include "GridNotifiersImpl.h"
|
||||
#include "Group.h"
|
||||
#include "Guild.h"
|
||||
#include "GuildMgr.h"
|
||||
#include "Language.h"
|
||||
#include "Log.h"
|
||||
#include "ObjectAccessor.h"
|
||||
#include "ObjectMgr.h"
|
||||
#include "Opcodes.h"
|
||||
#include "Player.h"
|
||||
#include "ScriptMgr.h"
|
||||
#include "SpellAuraEffects.h"
|
||||
#include "Util.h"
|
||||
#include "World.h"
|
||||
#include "WorldPacket.h"
|
||||
|
||||
void WorldSession::HandleMessagechatOpcode(WorldPacket& recvData)
|
||||
{
|
||||
uint32 type = 0;
|
||||
uint32 lang;
|
||||
|
||||
switch (recvData.GetOpcode())
|
||||
{
|
||||
case CMSG_MESSAGECHAT_SAY:
|
||||
type = CHAT_MSG_SAY;
|
||||
break;
|
||||
case CMSG_MESSAGECHAT_YELL:
|
||||
type = CHAT_MSG_YELL;
|
||||
break;
|
||||
case CMSG_MESSAGECHAT_CHANNEL:
|
||||
type = CHAT_MSG_CHANNEL;
|
||||
break;
|
||||
case CMSG_MESSAGECHAT_WHISPER:
|
||||
type = CHAT_MSG_WHISPER;
|
||||
break;
|
||||
case CMSG_MESSAGECHAT_GUILD:
|
||||
type = CHAT_MSG_GUILD;
|
||||
break;
|
||||
case CMSG_MESSAGECHAT_OFFICER:
|
||||
type = CHAT_MSG_OFFICER;
|
||||
break;
|
||||
case CMSG_MESSAGECHAT_AFK:
|
||||
type = CHAT_MSG_AFK;
|
||||
break;
|
||||
case CMSG_MESSAGECHAT_DND:
|
||||
type = CHAT_MSG_DND;
|
||||
break;
|
||||
case CMSG_MESSAGECHAT_EMOTE:
|
||||
type = CHAT_MSG_EMOTE;
|
||||
break;
|
||||
case CMSG_MESSAGECHAT_PARTY:
|
||||
type = CHAT_MSG_PARTY;
|
||||
break;
|
||||
case CMSG_MESSAGECHAT_RAID:
|
||||
type = CHAT_MSG_RAID;
|
||||
break;
|
||||
case CMSG_MESSAGECHAT_BATTLEGROUND:
|
||||
type = CHAT_MSG_BATTLEGROUND;
|
||||
break;
|
||||
case CMSG_MESSAGECHAT_RAID_WARNING:
|
||||
type = CHAT_MSG_RAID_WARNING;
|
||||
break;
|
||||
default:
|
||||
TC_LOG_ERROR("network", "HandleMessagechatOpcode : Unknown chat opcode (%u)", recvData.GetOpcode());
|
||||
recvData.hexlike();
|
||||
return;
|
||||
}
|
||||
|
||||
if (type >= MAX_CHAT_MSG_TYPE)
|
||||
{
|
||||
TC_LOG_ERROR("network", "CHAT: Wrong message type received: %u", type);
|
||||
recvData.rfinish();
|
||||
return;
|
||||
}
|
||||
|
||||
Player* sender = GetPlayer();
|
||||
|
||||
//TC_LOG_DEBUG("misc", "CHAT: packet received. type %u, lang %u", type, lang);
|
||||
|
||||
// no language sent with emote packet.
|
||||
if (type != CHAT_MSG_EMOTE && type != CHAT_MSG_AFK && type != CHAT_MSG_DND)
|
||||
{
|
||||
recvData >> lang;
|
||||
|
||||
if (lang == LANG_UNIVERSAL)
|
||||
{
|
||||
TC_LOG_INFO("entities.player.cheat", "CMSG_MESSAGECHAT: Possible hacking-attempt: %s tried to send a message in universal language", GetPlayerInfo().c_str());
|
||||
SendNotification(LANG_UNKNOWN_LANGUAGE);
|
||||
recvData.rfinish();
|
||||
return;
|
||||
}
|
||||
|
||||
// prevent talking at unknown language (cheating)
|
||||
LanguageDesc const* langDesc = GetLanguageDescByID(lang);
|
||||
if (!langDesc)
|
||||
{
|
||||
SendNotification(LANG_UNKNOWN_LANGUAGE);
|
||||
recvData.rfinish();
|
||||
return;
|
||||
}
|
||||
|
||||
if (langDesc->skill_id != 0 && !sender->HasSkill(langDesc->skill_id))
|
||||
{
|
||||
// also check SPELL_AURA_COMPREHEND_LANGUAGE (client offers option to speak in that language)
|
||||
Unit::AuraEffectList const& langAuras = sender->GetAuraEffectsByType(SPELL_AURA_COMPREHEND_LANGUAGE);
|
||||
bool foundAura = false;
|
||||
for (Unit::AuraEffectList::const_iterator i = langAuras.begin(); i != langAuras.end(); ++i)
|
||||
{
|
||||
if ((*i)->GetMiscValue() == int32(lang))
|
||||
{
|
||||
foundAura = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!foundAura)
|
||||
{
|
||||
SendNotification(LANG_NOT_LEARNED_LANGUAGE);
|
||||
recvData.rfinish();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (lang == LANG_ADDON)
|
||||
{
|
||||
// LANG_ADDON is only valid for the following message types
|
||||
switch (type)
|
||||
{
|
||||
case CHAT_MSG_PARTY:
|
||||
case CHAT_MSG_RAID:
|
||||
case CHAT_MSG_GUILD:
|
||||
case CHAT_MSG_BATTLEGROUND:
|
||||
case CHAT_MSG_WHISPER:
|
||||
// check if addon messages are disabled
|
||||
if (!sWorld->getBoolConfig(CONFIG_ADDON_CHANNEL))
|
||||
{
|
||||
recvData.rfinish();
|
||||
return;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
TC_LOG_ERROR("network", "Player %s (GUID: %u) sent a chatmessage with an invalid language/message type combination",
|
||||
GetPlayer()->GetName().c_str(), GetPlayer()->GetGUID().GetCounter());
|
||||
|
||||
recvData.rfinish();
|
||||
return;
|
||||
}
|
||||
}
|
||||
// LANG_ADDON should not be changed nor be affected by flood control
|
||||
else
|
||||
{
|
||||
// send in universal language if player in .gm on mode (ignore spell effects)
|
||||
if (sender->IsGameMaster())
|
||||
lang = LANG_UNIVERSAL;
|
||||
else
|
||||
{
|
||||
// send in universal language in two side iteration allowed mode
|
||||
if (HasPermission(rbac::RBAC_PERM_TWO_SIDE_INTERACTION_CHAT))
|
||||
lang = LANG_UNIVERSAL;
|
||||
else
|
||||
{
|
||||
switch (type)
|
||||
{
|
||||
case CHAT_MSG_PARTY:
|
||||
case CHAT_MSG_PARTY_LEADER:
|
||||
case CHAT_MSG_RAID:
|
||||
case CHAT_MSG_RAID_LEADER:
|
||||
case CHAT_MSG_RAID_WARNING:
|
||||
// allow two side chat at group channel if two side group allowed
|
||||
if (sWorld->getBoolConfig(CONFIG_ALLOW_TWO_SIDE_INTERACTION_GROUP))
|
||||
lang = LANG_UNIVERSAL;
|
||||
break;
|
||||
case CHAT_MSG_GUILD:
|
||||
case CHAT_MSG_OFFICER:
|
||||
// allow two side chat at guild channel if two side guild allowed
|
||||
if (sWorld->getBoolConfig(CONFIG_ALLOW_TWO_SIDE_INTERACTION_GUILD))
|
||||
lang = LANG_UNIVERSAL;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// but overwrite it by SPELL_AURA_MOD_LANGUAGE auras (only single case used)
|
||||
Unit::AuraEffectList const& ModLangAuras = sender->GetAuraEffectsByType(SPELL_AURA_MOD_LANGUAGE);
|
||||
if (!ModLangAuras.empty())
|
||||
lang = ModLangAuras.front()->GetMiscValue();
|
||||
}
|
||||
|
||||
if (!CanSpeak())
|
||||
{
|
||||
std::string timeStr = secsToTimeString(m_muteTime - GameTime::GetGameTime());
|
||||
SendNotification(GetTrinityString(LANG_WAIT_BEFORE_SPEAKING), timeStr.c_str());
|
||||
recvData.rfinish(); // Prevent warnings
|
||||
return;
|
||||
}
|
||||
|
||||
if (type != CHAT_MSG_AFK && type != CHAT_MSG_DND)
|
||||
sender->UpdateSpeakTime(Player::ChatFloodThrottle::REGULAR);
|
||||
}
|
||||
}
|
||||
else
|
||||
lang = LANG_UNIVERSAL;
|
||||
|
||||
if (sender->HasAura(1852) && type != CHAT_MSG_WHISPER)
|
||||
{
|
||||
SendNotification(GetTrinityString(LANG_GM_SILENCE), sender->GetName().c_str());
|
||||
recvData.rfinish();
|
||||
return;
|
||||
}
|
||||
|
||||
uint32 textLength = 0;
|
||||
uint32 receiverLength = 0;
|
||||
std::string to, channel, msg;
|
||||
bool ignoreChecks = false;
|
||||
switch (type)
|
||||
{
|
||||
case CHAT_MSG_SAY:
|
||||
case CHAT_MSG_EMOTE:
|
||||
case CHAT_MSG_YELL:
|
||||
case CHAT_MSG_PARTY:
|
||||
case CHAT_MSG_GUILD:
|
||||
case CHAT_MSG_OFFICER:
|
||||
case CHAT_MSG_RAID:
|
||||
case CHAT_MSG_RAID_WARNING:
|
||||
case CHAT_MSG_BATTLEGROUND:
|
||||
textLength = recvData.ReadBits(9);
|
||||
msg = recvData.ReadString(textLength);
|
||||
break;
|
||||
case CHAT_MSG_WHISPER:
|
||||
receiverLength = recvData.ReadBits(10);
|
||||
textLength = recvData.ReadBits(9);
|
||||
to = recvData.ReadString(receiverLength);
|
||||
msg = recvData.ReadString(textLength);
|
||||
break;
|
||||
case CHAT_MSG_CHANNEL:
|
||||
receiverLength = recvData.ReadBits(10);
|
||||
textLength = recvData.ReadBits(9);
|
||||
msg = recvData.ReadString(textLength);
|
||||
channel = recvData.ReadString(receiverLength);
|
||||
break;
|
||||
case CHAT_MSG_AFK:
|
||||
case CHAT_MSG_DND:
|
||||
textLength = recvData.ReadBits(9);
|
||||
msg = recvData.ReadString(textLength);
|
||||
ignoreChecks = true;
|
||||
break;
|
||||
}
|
||||
|
||||
if (!ignoreChecks)
|
||||
{
|
||||
if (msg.empty())
|
||||
return;
|
||||
|
||||
if (lang == LANG_ADDON)
|
||||
{
|
||||
if (AddonChannelCommandHandler(this).ParseCommands(msg.c_str()))
|
||||
return;
|
||||
}
|
||||
if (lang != LANG_ADDON)
|
||||
{
|
||||
if (ChatHandler(this).ParseCommands(msg.c_str()))
|
||||
return;
|
||||
// Strip invisible characters for non-addon messages
|
||||
if (sWorld->getBoolConfig(CONFIG_CHAT_FAKE_MESSAGE_PREVENTING))
|
||||
stripLineInvisibleChars(msg);
|
||||
|
||||
if (sWorld->getIntConfig(CONFIG_CHAT_STRICT_LINK_CHECKING_SEVERITY) && !ChatHandler(this).isValidChatMessage(msg.c_str()))
|
||||
{
|
||||
TC_LOG_ERROR("network", "Player %s (GUID: %u) sent a chatmessage with an invalid link: %s", GetPlayer()->GetName().c_str(),
|
||||
GetPlayer()->GetGUID().GetCounter(), msg.c_str());
|
||||
|
||||
if (sWorld->getIntConfig(CONFIG_CHAT_STRICT_LINK_CHECKING_KICK))
|
||||
KickPlayer();
|
||||
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
switch (type)
|
||||
{
|
||||
case CHAT_MSG_SAY:
|
||||
{
|
||||
// Prevent cheating
|
||||
if (!sender->IsAlive())
|
||||
return;
|
||||
|
||||
if (sender->getLevel() < sWorld->getIntConfig(CONFIG_CHAT_SAY_LEVEL_REQ))
|
||||
{
|
||||
SendNotification(GetTrinityString(LANG_SAY_REQ), sWorld->getIntConfig(CONFIG_CHAT_SAY_LEVEL_REQ));
|
||||
return;
|
||||
}
|
||||
|
||||
sender->Say(msg, Language(lang));
|
||||
break;
|
||||
}
|
||||
case CHAT_MSG_EMOTE:
|
||||
{
|
||||
// Prevent cheating
|
||||
if (!sender->IsAlive())
|
||||
return;
|
||||
|
||||
if (sender->getLevel() < sWorld->getIntConfig(CONFIG_CHAT_EMOTE_LEVEL_REQ))
|
||||
{
|
||||
SendNotification(GetTrinityString(LANG_SAY_REQ), sWorld->getIntConfig(CONFIG_CHAT_EMOTE_LEVEL_REQ));
|
||||
return;
|
||||
}
|
||||
|
||||
sender->TextEmote(msg);
|
||||
break;
|
||||
}
|
||||
case CHAT_MSG_YELL:
|
||||
{
|
||||
// Prevent cheating
|
||||
if (!sender->IsAlive())
|
||||
return;
|
||||
|
||||
if (sender->getLevel() < sWorld->getIntConfig(CONFIG_CHAT_YELL_LEVEL_REQ))
|
||||
{
|
||||
SendNotification(GetTrinityString(LANG_SAY_REQ), sWorld->getIntConfig(CONFIG_CHAT_YELL_LEVEL_REQ));
|
||||
return;
|
||||
}
|
||||
|
||||
sender->Yell(msg, Language(lang));
|
||||
break;
|
||||
}
|
||||
case CHAT_MSG_WHISPER:
|
||||
{
|
||||
if (!normalizePlayerName(to))
|
||||
{
|
||||
SendPlayerNotFoundNotice(to);
|
||||
break;
|
||||
}
|
||||
|
||||
Player* receiver = ObjectAccessor::FindConnectedPlayerByName(to);
|
||||
if (!receiver || (lang != LANG_ADDON && !receiver->isAcceptWhispers() && receiver->GetSession()->HasPermission(rbac::RBAC_PERM_CAN_FILTER_WHISPERS) && !receiver->IsInWhisperWhiteList(sender->GetGUID())))
|
||||
{
|
||||
SendPlayerNotFoundNotice(to);
|
||||
return;
|
||||
}
|
||||
if (!sender->IsGameMaster() && sender->getLevel() < sWorld->getIntConfig(CONFIG_CHAT_WHISPER_LEVEL_REQ) && !receiver->IsInWhisperWhiteList(sender->GetGUID()))
|
||||
{
|
||||
SendNotification(GetTrinityString(LANG_WHISPER_REQ), sWorld->getIntConfig(CONFIG_CHAT_WHISPER_LEVEL_REQ));
|
||||
return;
|
||||
}
|
||||
|
||||
if (GetPlayer()->GetTeam() != receiver->GetTeam() && !HasPermission(rbac::RBAC_PERM_TWO_SIDE_INTERACTION_CHAT) && !receiver->IsInWhisperWhiteList(sender->GetGUID()))
|
||||
{
|
||||
SendWrongFactionNotice();
|
||||
return;
|
||||
}
|
||||
|
||||
if (GetPlayer()->HasAura(1852) && !receiver->IsGameMaster())
|
||||
{
|
||||
SendNotification(GetTrinityString(LANG_GM_SILENCE), GetPlayer()->GetName().c_str());
|
||||
return;
|
||||
}
|
||||
|
||||
// If player is a Gamemaster and doesn't accept whisper, we auto-whitelist every player that the Gamemaster is talking to
|
||||
// We also do that if a player is under the required level for whispers.
|
||||
if (receiver->getLevel() < sWorld->getIntConfig(CONFIG_CHAT_WHISPER_LEVEL_REQ) ||
|
||||
(HasPermission(rbac::RBAC_PERM_CAN_FILTER_WHISPERS) && !sender->isAcceptWhispers() && !sender->IsInWhisperWhiteList(receiver->GetGUID())))
|
||||
sender->AddWhisperWhiteList(receiver->GetGUID());
|
||||
|
||||
GetPlayer()->Whisper(msg, Language(lang), receiver);
|
||||
break;
|
||||
}
|
||||
case CHAT_MSG_PARTY:
|
||||
case CHAT_MSG_PARTY_LEADER:
|
||||
{
|
||||
// if player is in battleground, he cannot say to battleground members by /p
|
||||
Group* group = GetPlayer()->GetOriginalGroup();
|
||||
if (!group)
|
||||
{
|
||||
group = sender->GetGroup();
|
||||
if (!group || group->isBGGroup())
|
||||
return;
|
||||
}
|
||||
|
||||
if (group->IsLeader(GetPlayer()->GetGUID()))
|
||||
type = CHAT_MSG_PARTY_LEADER;
|
||||
|
||||
sScriptMgr->OnPlayerChat(GetPlayer(), type, lang, msg, group);
|
||||
|
||||
WorldPacket data;
|
||||
ChatHandler::BuildChatPacket(data, ChatMsg(type), Language(lang), sender, nullptr, msg);
|
||||
group->BroadcastPacket(&data, false, group->GetMemberGroup(GetPlayer()->GetGUID()));
|
||||
break;
|
||||
}
|
||||
case CHAT_MSG_GUILD:
|
||||
{
|
||||
if (GetPlayer()->GetGuildId())
|
||||
{
|
||||
if (Guild* guild = sGuildMgr->GetGuildById(GetPlayer()->GetGuildId()))
|
||||
{
|
||||
sScriptMgr->OnPlayerChat(GetPlayer(), type, lang, msg, guild);
|
||||
|
||||
guild->BroadcastToGuild(this, false, msg, lang == LANG_ADDON ? LANG_ADDON : LANG_UNIVERSAL);
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
case CHAT_MSG_OFFICER:
|
||||
{
|
||||
if (GetPlayer()->GetGuildId())
|
||||
{
|
||||
if (Guild* guild = sGuildMgr->GetGuildById(GetPlayer()->GetGuildId()))
|
||||
{
|
||||
sScriptMgr->OnPlayerChat(GetPlayer(), type, lang, msg, guild);
|
||||
|
||||
guild->BroadcastToGuild(this, true, msg, lang == LANG_ADDON ? LANG_ADDON : LANG_UNIVERSAL);
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
case CHAT_MSG_RAID:
|
||||
case CHAT_MSG_RAID_LEADER:
|
||||
{
|
||||
// if player is in battleground, he cannot say to battleground members by /ra
|
||||
Group* group = GetPlayer()->GetOriginalGroup();
|
||||
if (!group)
|
||||
{
|
||||
group = GetPlayer()->GetGroup();
|
||||
if (!group || group->isBGGroup() || !group->isRaidGroup())
|
||||
return;
|
||||
}
|
||||
|
||||
if (group->IsLeader(GetPlayer()->GetGUID()))
|
||||
type = CHAT_MSG_RAID_LEADER;
|
||||
|
||||
sScriptMgr->OnPlayerChat(GetPlayer(), type, lang, msg, group);
|
||||
|
||||
WorldPacket data;
|
||||
ChatHandler::BuildChatPacket(data, ChatMsg(type), Language(lang), sender, nullptr, msg);
|
||||
group->BroadcastPacket(&data, false);
|
||||
break;
|
||||
}
|
||||
case CHAT_MSG_RAID_WARNING:
|
||||
{
|
||||
Group* group = GetPlayer()->GetGroup();
|
||||
if (!group || !(group->isRaidGroup() || sWorld->getBoolConfig(CONFIG_CHAT_PARTY_RAID_WARNINGS)) || !(group->IsLeader(GetPlayer()->GetGUID()) || group->IsAssistant(GetPlayer()->GetGUID())) || group->isBGGroup())
|
||||
return;
|
||||
|
||||
sScriptMgr->OnPlayerChat(GetPlayer(), type, lang, msg, group);
|
||||
|
||||
WorldPacket data;
|
||||
//in battleground, raid warning is sent only to players in battleground - code is ok
|
||||
ChatHandler::BuildChatPacket(data, CHAT_MSG_RAID_WARNING, Language(lang), sender, nullptr, msg);
|
||||
group->BroadcastPacket(&data, false);
|
||||
break;
|
||||
}
|
||||
case CHAT_MSG_BATTLEGROUND:
|
||||
case CHAT_MSG_BATTLEGROUND_LEADER:
|
||||
{
|
||||
// battleground raid is always in Player->GetGroup(), never in GetOriginalGroup()
|
||||
Group* group = GetPlayer()->GetGroup();
|
||||
if (!group || !group->isBGGroup())
|
||||
return;
|
||||
|
||||
if (group->IsLeader(GetPlayer()->GetGUID()))
|
||||
type = CHAT_MSG_BATTLEGROUND_LEADER;
|
||||
|
||||
sScriptMgr->OnPlayerChat(GetPlayer(), type, lang, msg, group);
|
||||
|
||||
WorldPacket data;
|
||||
ChatHandler::BuildChatPacket(data, ChatMsg(type), Language(lang), sender, nullptr, msg);
|
||||
group->BroadcastPacket(&data, false);
|
||||
break;
|
||||
}
|
||||
case CHAT_MSG_CHANNEL:
|
||||
{
|
||||
if (!HasPermission(rbac::RBAC_PERM_SKIP_CHECK_CHAT_CHANNEL_REQ))
|
||||
{
|
||||
if (sender->getLevel() < sWorld->getIntConfig(CONFIG_CHAT_CHANNEL_LEVEL_REQ))
|
||||
{
|
||||
SendNotification(GetTrinityString(LANG_CHANNEL_REQ), sWorld->getIntConfig(CONFIG_CHAT_CHANNEL_LEVEL_REQ));
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (Channel* chn = ChannelMgr::GetChannelForPlayerByNamePart(channel, sender))
|
||||
{
|
||||
sScriptMgr->OnPlayerChat(sender, type, lang, msg, chn);
|
||||
chn->Say(sender->GetGUID(), msg, lang);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case CHAT_MSG_AFK:
|
||||
{
|
||||
if (!sender->IsInCombat())
|
||||
{
|
||||
if (sender ->isAFK()) // Already AFK
|
||||
{
|
||||
if (msg.empty())
|
||||
sender->ToggleAFK(); // Remove AFK
|
||||
else
|
||||
sender->autoReplyMsg = msg; // Update message
|
||||
}
|
||||
else // New AFK mode
|
||||
{
|
||||
sender->autoReplyMsg = msg.empty() ? GetTrinityString(LANG_PLAYER_AFK_DEFAULT) : msg;
|
||||
|
||||
if (sender->isDND())
|
||||
sender->ToggleDND();
|
||||
|
||||
sender->ToggleAFK();
|
||||
}
|
||||
|
||||
sScriptMgr->OnPlayerChat(sender, type, lang, msg);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case CHAT_MSG_DND:
|
||||
{
|
||||
if (sender->isDND()) // Already DND
|
||||
{
|
||||
if (msg.empty())
|
||||
sender->ToggleDND(); // Remove DND
|
||||
else
|
||||
sender->autoReplyMsg = msg; // Update message
|
||||
}
|
||||
else // New DND mode
|
||||
{
|
||||
sender->autoReplyMsg = msg.empty() ? GetTrinityString(LANG_PLAYER_DND_DEFAULT) : msg;
|
||||
|
||||
if (sender->isAFK())
|
||||
sender->ToggleAFK();
|
||||
|
||||
sender->ToggleDND();
|
||||
}
|
||||
|
||||
sScriptMgr->OnPlayerChat(sender, type, lang, msg);
|
||||
break;
|
||||
}
|
||||
default:
|
||||
TC_LOG_ERROR("network", "CHAT: unknown message type %u, lang: %u", type, lang);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void WorldSession::HandleAddonMessagechatOpcode(WorldPacket& recvData)
|
||||
{
|
||||
Player* sender = GetPlayer();
|
||||
ChatMsg type;
|
||||
|
||||
switch (recvData.GetOpcode())
|
||||
{
|
||||
case CMSG_MESSAGECHAT_ADDON_BATTLEGROUND:
|
||||
type = CHAT_MSG_BATTLEGROUND;
|
||||
break;
|
||||
case CMSG_MESSAGECHAT_ADDON_GUILD:
|
||||
type = CHAT_MSG_GUILD;
|
||||
break;
|
||||
case CMSG_MESSAGECHAT_ADDON_OFFICER:
|
||||
type = CHAT_MSG_OFFICER;
|
||||
break;
|
||||
case CMSG_MESSAGECHAT_ADDON_PARTY:
|
||||
type = CHAT_MSG_PARTY;
|
||||
break;
|
||||
case CMSG_MESSAGECHAT_ADDON_RAID:
|
||||
type = CHAT_MSG_RAID;
|
||||
break;
|
||||
case CMSG_MESSAGECHAT_ADDON_WHISPER:
|
||||
type = CHAT_MSG_WHISPER;
|
||||
break;
|
||||
default:
|
||||
TC_LOG_ERROR("network", "HandleAddonMessagechatOpcode: Unknown addon chat opcode (%u)", recvData.GetOpcode());
|
||||
recvData.hexlike();
|
||||
return;
|
||||
}
|
||||
|
||||
std::string message;
|
||||
std::string prefix;
|
||||
std::string targetName;
|
||||
|
||||
switch (type)
|
||||
{
|
||||
case CHAT_MSG_WHISPER:
|
||||
{
|
||||
uint32 msgLen = recvData.ReadBits(9);
|
||||
uint32 prefixLen = recvData.ReadBits(5);
|
||||
uint32 targetLen = recvData.ReadBits(10);
|
||||
message = recvData.ReadString(msgLen);
|
||||
prefix = recvData.ReadString(prefixLen);
|
||||
targetName = recvData.ReadString(targetLen);
|
||||
break;
|
||||
}
|
||||
case CHAT_MSG_RAID:
|
||||
case CHAT_MSG_BATTLEGROUND:
|
||||
{
|
||||
uint32 prefixLen = recvData.ReadBits(5);
|
||||
uint32 msgLen = recvData.ReadBits(9);
|
||||
prefix = recvData.ReadString(prefixLen);
|
||||
message = recvData.ReadString(msgLen);
|
||||
break;
|
||||
}
|
||||
case CHAT_MSG_PARTY:
|
||||
case CHAT_MSG_OFFICER:
|
||||
{
|
||||
uint32 prefixLen = recvData.ReadBits(5);
|
||||
uint32 msgLen = recvData.ReadBits(9);
|
||||
message = recvData.ReadString(msgLen);
|
||||
prefix = recvData.ReadString(prefixLen);
|
||||
break;
|
||||
}
|
||||
case CHAT_MSG_GUILD:
|
||||
{
|
||||
uint32 msgLen = recvData.ReadBits(9);
|
||||
uint32 prefixLen = recvData.ReadBits(5);
|
||||
message = recvData.ReadString(msgLen);
|
||||
prefix = recvData.ReadString(prefixLen);
|
||||
break;
|
||||
}
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
if (prefix.empty() || prefix.length() > 16)
|
||||
{
|
||||
recvData.rfinish();
|
||||
return;
|
||||
}
|
||||
|
||||
// Disabled addon channel?
|
||||
if (!sWorld->getBoolConfig(CONFIG_ADDON_CHANNEL))
|
||||
{
|
||||
recvData.rfinish();
|
||||
return;
|
||||
}
|
||||
|
||||
if (!CanSpeak())
|
||||
{
|
||||
recvData.rfinish();
|
||||
return;
|
||||
}
|
||||
|
||||
sender->UpdateSpeakTime(Player::ChatFloodThrottle::ADDON);
|
||||
|
||||
switch (type)
|
||||
{
|
||||
case CHAT_MSG_BATTLEGROUND:
|
||||
{
|
||||
Group* group = sender->GetGroup();
|
||||
if (!group || !group->isBGGroup())
|
||||
return;
|
||||
|
||||
WorldPacket data;
|
||||
ChatHandler::BuildChatPacket(data, type, LANG_ADDON, sender, nullptr, message, 0U, "", DEFAULT_LOCALE, prefix);
|
||||
group->BroadcastAddonMessagePacket(&data, prefix, false);
|
||||
break;
|
||||
}
|
||||
case CHAT_MSG_GUILD:
|
||||
case CHAT_MSG_OFFICER:
|
||||
{
|
||||
if (sender->GetGuildId())
|
||||
if (Guild* guild = sGuildMgr->GetGuildById(sender->GetGuildId()))
|
||||
guild->BroadcastAddonToGuild(this, type == CHAT_MSG_OFFICER, message, prefix);
|
||||
break;
|
||||
}
|
||||
case CHAT_MSG_WHISPER:
|
||||
{
|
||||
if (!normalizePlayerName(targetName))
|
||||
break;
|
||||
Player* receiver = ObjectAccessor::FindPlayerByName(targetName);
|
||||
if (!receiver)
|
||||
break;
|
||||
|
||||
sender->WhisperAddon(message, prefix, receiver);
|
||||
break;
|
||||
}
|
||||
// Messages sent to "RAID" while in a party will get delivered to "PARTY"
|
||||
case CHAT_MSG_PARTY:
|
||||
case CHAT_MSG_RAID:
|
||||
{
|
||||
Group* group = sender->GetGroup();
|
||||
if (!group || group->isBGGroup())
|
||||
break;
|
||||
|
||||
WorldPacket data;
|
||||
ChatHandler::BuildChatPacket(data, type, LANG_ADDON, sender, nullptr, message, 0U, "", DEFAULT_LOCALE, prefix);
|
||||
group->BroadcastAddonMessagePacket(&data, prefix, true, -1, group->GetMemberGroup(sender->GetGUID()));
|
||||
break;
|
||||
}
|
||||
default:
|
||||
{
|
||||
TC_LOG_ERROR("misc", "HandleAddonMessagechatOpcode: unknown addon message type %u", type);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void WorldSession::HandleEmoteOpcode(WorldPacket& recvData)
|
||||
{
|
||||
if (!GetPlayer()->IsAlive() || GetPlayer()->HasUnitState(UNIT_STATE_DIED))
|
||||
return;
|
||||
|
||||
uint32 emote;
|
||||
recvData >> emote;
|
||||
|
||||
// restrict to the only emotes hardcoded in client
|
||||
if (emote != EMOTE_ONESHOT_NONE && emote != EMOTE_ONESHOT_WAVE)
|
||||
return;
|
||||
|
||||
sScriptMgr->OnPlayerClearEmote(GetPlayer());
|
||||
|
||||
if (_player->GetUInt32Value(UNIT_NPC_EMOTESTATE))
|
||||
_player->SetUInt32Value(UNIT_NPC_EMOTESTATE, 0);
|
||||
}
|
||||
|
||||
void WorldSession::HandleSendTextEmoteOpcode(WorldPackets::Chat::SendTextEmote& packet)
|
||||
{
|
||||
if (!GetPlayer()->IsAlive())
|
||||
return;
|
||||
|
||||
if (!CanSpeak())
|
||||
{
|
||||
std::string timeStr = secsToTimeString(m_muteTime - GameTime::GetGameTime());
|
||||
SendNotification(GetTrinityString(LANG_WAIT_BEFORE_SPEAKING), timeStr.c_str());
|
||||
return;
|
||||
}
|
||||
|
||||
sScriptMgr->OnPlayerTextEmote(GetPlayer(), packet.SoundIndex, packet.EmoteID, packet.Target);
|
||||
|
||||
EmotesTextEntry const* em = sEmotesTextStore.LookupEntry(packet.EmoteID);
|
||||
if (!em)
|
||||
return;
|
||||
|
||||
Emote emote = static_cast<Emote>(em->EmoteID);
|
||||
|
||||
switch (emote)
|
||||
{
|
||||
case EMOTE_STATE_SLEEP:
|
||||
case EMOTE_STATE_SIT:
|
||||
case EMOTE_STATE_KNEEL:
|
||||
case EMOTE_ONESHOT_NONE:
|
||||
break;
|
||||
case EMOTE_STATE_DANCE:
|
||||
case EMOTE_STATE_READ:
|
||||
GetPlayer()->SetUInt32Value(UNIT_NPC_EMOTESTATE, emote);
|
||||
break;
|
||||
default:
|
||||
// Only allow text-emotes for "dead" entities (feign death included)
|
||||
if (GetPlayer()->HasUnitState(UNIT_STATE_DIED))
|
||||
break;
|
||||
GetPlayer()->HandleEmoteCommand(emote);
|
||||
break;
|
||||
}
|
||||
|
||||
Unit* unit = ObjectAccessor::GetUnit(*_player, packet.Target);
|
||||
|
||||
WorldPackets::Chat::STextEmote textEmote;
|
||||
textEmote.SourceGUID = _player->GetGUID();
|
||||
textEmote.EmoteID = packet.EmoteID;
|
||||
textEmote.SoundIndex = packet.SoundIndex;
|
||||
if (unit)
|
||||
textEmote.Target = unit->GetName();
|
||||
|
||||
_player->SendMessageToSetInRange(textEmote.Write(), sWorld->getFloatConfig(CONFIG_LISTEN_RANGE_TEXTEMOTE), true);
|
||||
|
||||
GetPlayer()->UpdateAchievementCriteria(ACHIEVEMENT_CRITERIA_TYPE_DO_EMOTE, packet.EmoteID, 0, 0, unit);
|
||||
|
||||
//Send scripted event call
|
||||
if (unit && unit->GetTypeId() == TYPEID_UNIT && ((Creature*)unit)->AI())
|
||||
((Creature*)unit)->AI()->ReceiveEmote(GetPlayer(), packet.EmoteID);
|
||||
|
||||
if (emote != EMOTE_ONESHOT_NONE)
|
||||
_player->RemoveAurasWithInterruptFlags(SpellAuraInterruptFlags::Anim);
|
||||
}
|
||||
|
||||
void WorldSession::HandleChatIgnoredOpcode(WorldPacket& recvData)
|
||||
{
|
||||
ObjectGuid guid;
|
||||
uint8 unk;
|
||||
//TC_LOG_DEBUG("network", "WORLD: Received CMSG_CHAT_IGNORED");
|
||||
|
||||
recvData >> unk; // probably related to spam reporting
|
||||
guid[5] = recvData.ReadBit();
|
||||
guid[2] = recvData.ReadBit();
|
||||
guid[6] = recvData.ReadBit();
|
||||
guid[4] = recvData.ReadBit();
|
||||
guid[7] = recvData.ReadBit();
|
||||
guid[0] = recvData.ReadBit();
|
||||
guid[1] = recvData.ReadBit();
|
||||
guid[3] = recvData.ReadBit();
|
||||
|
||||
recvData.ReadByteSeq(guid[0]);
|
||||
recvData.ReadByteSeq(guid[6]);
|
||||
recvData.ReadByteSeq(guid[5]);
|
||||
recvData.ReadByteSeq(guid[1]);
|
||||
recvData.ReadByteSeq(guid[4]);
|
||||
recvData.ReadByteSeq(guid[3]);
|
||||
recvData.ReadByteSeq(guid[7]);
|
||||
recvData.ReadByteSeq(guid[2]);
|
||||
|
||||
Player* player = ObjectAccessor::FindConnectedPlayer(guid);
|
||||
if (!player || !player->GetSession())
|
||||
return;
|
||||
|
||||
WorldPacket data;
|
||||
ChatHandler::BuildChatPacket(data, CHAT_MSG_IGNORED, LANG_UNIVERSAL, _player, _player, GetPlayer()->GetName());
|
||||
player->SendDirectMessage(&data);
|
||||
}
|
||||
|
||||
void WorldSession::HandleChannelDeclineInvite(WorldPacket &recvPacket)
|
||||
{
|
||||
TC_LOG_DEBUG("network", "Opcode %u", recvPacket.GetOpcode());
|
||||
}
|
||||
|
||||
void WorldSession::SendPlayerNotFoundNotice(std::string const& name)
|
||||
{
|
||||
WorldPacket data(SMSG_CHAT_PLAYER_NOT_FOUND, name.size()+1);
|
||||
data << name;
|
||||
SendPacket(&data);
|
||||
}
|
||||
|
||||
void WorldSession::SendPlayerAmbiguousNotice(std::string const& name)
|
||||
{
|
||||
WorldPacket data(SMSG_CHAT_PLAYER_AMBIGUOUS, name.size()+1);
|
||||
data << name;
|
||||
SendPacket(&data);
|
||||
}
|
||||
|
||||
void WorldSession::SendWrongFactionNotice()
|
||||
{
|
||||
WorldPacket data(SMSG_CHAT_WRONG_FACTION, 0);
|
||||
SendPacket(&data);
|
||||
}
|
||||
|
||||
void WorldSession::SendChatRestrictedNotice(ChatRestrictionType restriction)
|
||||
{
|
||||
WorldPacket data(SMSG_CHAT_RESTRICTED, 1);
|
||||
data << uint8(restriction);
|
||||
SendPacket(&data);
|
||||
}
|
||||
@@ -552,13 +552,25 @@ void WorldSession::HandleRaidTargetUpdateOpcode(WorldPacket& recvData)
|
||||
{
|
||||
TC_LOG_DEBUG("network", "WORLD: Received MSG_RAID_TARGET_UPDATE");
|
||||
|
||||
Group* group = GetPlayer()->GetGroup();
|
||||
if (!group)
|
||||
return;
|
||||
|
||||
// VIANO_VIRTUAL_TARGET_ICONS_V1
|
||||
// Read the icon byte before requiring a real Trinity Group. The client can
|
||||
// place raid target icons while Viano provides only virtual bot members.
|
||||
uint8 x;
|
||||
recvData >> x;
|
||||
|
||||
Group* group = GetPlayer()->GetGroup();
|
||||
if (!group)
|
||||
{
|
||||
// Query packet (0xFF) has no GUID payload.
|
||||
if (x == 0xFF)
|
||||
return;
|
||||
|
||||
ObjectGuid guid;
|
||||
recvData >> guid;
|
||||
|
||||
GetPlayer()->SetVianoTargetIcon(x, guid);
|
||||
return;
|
||||
}
|
||||
/** error handling **/
|
||||
/********************/
|
||||
|
||||
@@ -581,6 +593,7 @@ void WorldSession::HandleRaidTargetUpdateOpcode(WorldPacket& recvData)
|
||||
return;
|
||||
}
|
||||
|
||||
_player->SetVianoTargetIcon(x, guid);
|
||||
group->SetTargetIcon(x, _player->GetGUID(), guid);
|
||||
}
|
||||
}
|
||||
|
||||
+874
@@ -0,0 +1,874 @@
|
||||
/*
|
||||
* This file is part of the TrinityCore 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 "WorldSession.h"
|
||||
#include "CharacterCache.h"
|
||||
#include "Common.h"
|
||||
#include "DatabaseEnv.h"
|
||||
#include "Group.h"
|
||||
#include "GroupMgr.h"
|
||||
#include "Log.h"
|
||||
#include "ObjectAccessor.h"
|
||||
#include "ObjectMgr.h"
|
||||
#include "PartyPackets.h"
|
||||
#include "Pet.h"
|
||||
#include "PhasingHandler.h"
|
||||
#include "Player.h"
|
||||
#include "SocialMgr.h"
|
||||
#include "SpellAuraEffects.h"
|
||||
#include "SpellAuras.h"
|
||||
#include "Util.h"
|
||||
#include "Vehicle.h"
|
||||
#include "World.h"
|
||||
#include "WorldPacket.h"
|
||||
|
||||
class Aura;
|
||||
|
||||
/* differeces from off:
|
||||
-you can uninvite yourself - is is useful
|
||||
-you can accept invitation even if leader went offline
|
||||
*/
|
||||
/* todo:
|
||||
-group_destroyed msg is sent but not shown
|
||||
-reduce xp gaining when in raid group
|
||||
-quest sharing has to be corrected
|
||||
-FIX sending PartyMemberStats
|
||||
*/
|
||||
|
||||
void WorldSession::SendPartyResult(PartyOperation operation, const std::string& member, PartyResult res, uint32 val /* = 0 */)
|
||||
{
|
||||
WorldPackets::Party::PartyCommandResult packet;
|
||||
packet.Name = member;
|
||||
packet.Command = uint8(operation);
|
||||
packet.Result = uint8(res);
|
||||
packet.ResultData = val;
|
||||
packet.ResultGUID = ObjectGuid::Empty;
|
||||
|
||||
SendPacket(packet.Write());
|
||||
}
|
||||
|
||||
void WorldSession::HandlePartyInviteOpcode(WorldPackets::Party::PartyInviteClient& packet)
|
||||
{
|
||||
// cheating
|
||||
if (!normalizePlayerName(packet.TargetName))
|
||||
{
|
||||
SendPartyResult(PARTY_OP_INVITE, packet.TargetName, ERR_BAD_PLAYER_NAME_S);
|
||||
return;
|
||||
}
|
||||
|
||||
Player* invitingPlayer = GetPlayer();
|
||||
Player* invitedPlayer = ObjectAccessor::FindPlayerByName(packet.TargetName);
|
||||
|
||||
// no player
|
||||
if (!invitedPlayer)
|
||||
{
|
||||
SendPartyResult(PARTY_OP_INVITE, packet.TargetName, ERR_BAD_PLAYER_NAME_S);
|
||||
return;
|
||||
}
|
||||
|
||||
// player trying to invite himself (most likely cheating)
|
||||
if (invitedPlayer == invitingPlayer)
|
||||
{
|
||||
SendPartyResult(PARTY_OP_INVITE, packet.TargetName, ERR_BAD_PLAYER_NAME_S);
|
||||
return;
|
||||
}
|
||||
|
||||
// restrict invite to GMs
|
||||
if (!sWorld->getBoolConfig(CONFIG_ALLOW_GM_GROUP) && !invitingPlayer->IsGameMaster() && invitedPlayer->IsGameMaster())
|
||||
{
|
||||
SendPartyResult(PARTY_OP_INVITE, packet.TargetName, ERR_BAD_PLAYER_NAME_S);
|
||||
return;
|
||||
}
|
||||
|
||||
// can't group with
|
||||
if (!invitingPlayer->IsGameMaster() && !sWorld->getBoolConfig(CONFIG_ALLOW_TWO_SIDE_INTERACTION_GROUP) && invitingPlayer->GetTeam() != invitedPlayer->GetTeam())
|
||||
{
|
||||
SendPartyResult(PARTY_OP_INVITE, packet.TargetName, ERR_PLAYER_WRONG_FACTION);
|
||||
return;
|
||||
}
|
||||
if (invitingPlayer->GetInstanceId() != 0 && invitedPlayer->GetInstanceId() != 0 && invitingPlayer->GetInstanceId() != invitedPlayer->GetInstanceId() && invitingPlayer->GetMapId() == invitedPlayer->GetMapId())
|
||||
{
|
||||
SendPartyResult(PARTY_OP_INVITE, packet.TargetName, ERR_TARGET_NOT_IN_INSTANCE_S);
|
||||
return;
|
||||
}
|
||||
// just ignore us
|
||||
if (invitedPlayer->GetInstanceId() != 0 && invitedPlayer->GetDungeonDifficulty() != invitingPlayer->GetDungeonDifficulty())
|
||||
{
|
||||
SendPartyResult(PARTY_OP_INVITE, packet.TargetName, ERR_IGNORING_YOU_S);
|
||||
return;
|
||||
}
|
||||
|
||||
if (invitedPlayer->GetSocial()->HasIgnore(invitingPlayer->GetGUID()))
|
||||
{
|
||||
SendPartyResult(PARTY_OP_INVITE, packet.TargetName, ERR_IGNORING_YOU_S);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!invitedPlayer->GetSocial()->HasFriend(invitingPlayer->GetGUID()) && invitingPlayer->getLevel() < sWorld->getIntConfig(CONFIG_PARTY_LEVEL_REQ))
|
||||
{
|
||||
SendPartyResult(PARTY_OP_INVITE, packet.TargetName, ERR_INVITE_RESTRICTED);
|
||||
return;
|
||||
}
|
||||
|
||||
Group* group = invitingPlayer->GetGroup();
|
||||
if (group && group->isBGGroup())
|
||||
group = invitingPlayer->GetOriginalGroup();
|
||||
if (!group)
|
||||
group = invitingPlayer->GetGroupInvite();
|
||||
|
||||
Group* group2 = invitedPlayer->GetGroup();
|
||||
if (group2 && group2->isBGGroup())
|
||||
group2 = invitedPlayer->GetOriginalGroup();
|
||||
// player already in another group or invited
|
||||
if (group2 || invitedPlayer->GetGroupInvite())
|
||||
{
|
||||
SendPartyResult(PARTY_OP_INVITE, packet.TargetName, ERR_ALREADY_IN_GROUP_S);
|
||||
|
||||
if (group2)
|
||||
{
|
||||
// tell the player that they were invited but it failed as they were already in a group
|
||||
WorldPackets::Party::PartyInvite partyInvite;
|
||||
partyInvite.Initialize(invitingPlayer, packet.ProposedRoles, false);
|
||||
invitedPlayer->SendDirectMessage(partyInvite.Write());
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (group)
|
||||
{
|
||||
// not have permissions for invite
|
||||
if (!group->IsLeader(invitingPlayer->GetGUID()) && !group->IsAssistant(invitingPlayer->GetGUID()))
|
||||
{
|
||||
if (group->IsCreated())
|
||||
SendPartyResult(PARTY_OP_INVITE, "", ERR_NOT_LEADER);
|
||||
return;
|
||||
}
|
||||
// not have place
|
||||
if (group->IsFull())
|
||||
{
|
||||
SendPartyResult(PARTY_OP_INVITE, "", ERR_GROUP_FULL);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// ok, but group not exist, start a new group
|
||||
// but don't create and save the group to the DB until
|
||||
// at least one person joins
|
||||
if (!group)
|
||||
{
|
||||
group = new Group();
|
||||
// new group: if can't add then delete
|
||||
if (!group->AddLeaderInvite(invitingPlayer))
|
||||
{
|
||||
delete group;
|
||||
return;
|
||||
}
|
||||
if (!group->AddInvite(invitedPlayer))
|
||||
{
|
||||
group->RemoveAllInvites();
|
||||
delete group;
|
||||
return;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// already existed group: if can't add then just leave
|
||||
if (!group->AddInvite(invitedPlayer))
|
||||
{
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
WorldPackets::Party::PartyInvite partyInvite;
|
||||
partyInvite.Initialize(invitingPlayer, packet.ProposedRoles, true);
|
||||
invitedPlayer->SendDirectMessage(partyInvite.Write());
|
||||
|
||||
SendPartyResult(PARTY_OP_INVITE, packet.TargetName, ERR_PARTY_RESULT_OK);
|
||||
}
|
||||
|
||||
void WorldSession::HandlePartyInviteResponseOpcode(WorldPackets::Party::PartyInviteResponse& packet)
|
||||
{
|
||||
Group* group = GetPlayer()->GetGroupInvite();
|
||||
|
||||
if (!group)
|
||||
return;
|
||||
|
||||
if (packet.Accept)
|
||||
{
|
||||
// Remove player from invitees in any case
|
||||
group->RemoveInvite(GetPlayer());
|
||||
|
||||
if (group->GetLeaderGUID() == GetPlayer()->GetGUID())
|
||||
{
|
||||
TC_LOG_ERROR("network", "HandleGroupAcceptOpcode: player %s(%d) tried to accept an invite to his own group", GetPlayer()->GetName().c_str(), GetPlayer()->GetGUID().GetCounter());
|
||||
return;
|
||||
}
|
||||
|
||||
// Group is full
|
||||
if (group->IsFull())
|
||||
{
|
||||
SendPartyResult(PARTY_OP_INVITE, "", ERR_GROUP_FULL);
|
||||
return;
|
||||
}
|
||||
|
||||
Player* leader = ObjectAccessor::FindPlayer(group->GetLeaderGUID());
|
||||
|
||||
// Forming a new group, create it
|
||||
if (!group->IsCreated())
|
||||
{
|
||||
// This can happen if the leader is zoning. To be removed once delayed actions for zoning are implemented
|
||||
if (!leader)
|
||||
{
|
||||
group->RemoveAllInvites();
|
||||
return;
|
||||
}
|
||||
|
||||
// If we're about to create a group there really should be a leader present
|
||||
ASSERT(leader);
|
||||
group->RemoveInvite(leader);
|
||||
group->Create(leader);
|
||||
sGroupMgr->AddGroup(group);
|
||||
}
|
||||
|
||||
// Everything is fine, do it, PLAYER'S GROUP IS SET IN ADDMEMBER!!!
|
||||
if (!group->AddMember(GetPlayer()))
|
||||
return;
|
||||
|
||||
group->BroadcastGroupUpdate();
|
||||
}
|
||||
else
|
||||
{
|
||||
// Remember leader if online (group pointer will be invalid if group gets disbanded)
|
||||
Player* leader = ObjectAccessor::FindConnectedPlayer(group->GetLeaderGUID());
|
||||
|
||||
// uninvite, group can be deleted
|
||||
GetPlayer()->UninviteFromGroup();
|
||||
|
||||
if (!leader || !leader->GetSession())
|
||||
return;
|
||||
|
||||
// report
|
||||
WorldPackets::Party::GroupDecline decline(GetPlayer()->GetName());
|
||||
leader->SendDirectMessage(decline.Write());
|
||||
}
|
||||
}
|
||||
|
||||
void WorldSession::HandleGroupClearMarker(WorldPacket& recv_data)
|
||||
{
|
||||
TC_LOG_DEBUG("network", "WORLD: Received CMSG_CLEAR_RAID_MARKER");
|
||||
|
||||
uint8 marker;
|
||||
recv_data >> marker;
|
||||
|
||||
if (Group* group = GetPlayer()->GetGroup())
|
||||
{
|
||||
group->RemoveGroupMarkerMask(1 << marker);
|
||||
group->SendRaidMarkerUpdate();
|
||||
group->RemoveMarker();
|
||||
}
|
||||
}
|
||||
|
||||
void WorldSession::HandleGroupUninviteGuidOpcode(WorldPacket& recvData)
|
||||
{
|
||||
TC_LOG_DEBUG("network", "WORLD: Received CMSG_GROUP_UNINVITE_GUID");
|
||||
|
||||
ObjectGuid guid;
|
||||
std::string reason;
|
||||
recvData >> guid;
|
||||
recvData >> reason;
|
||||
|
||||
//can't uninvite yourself
|
||||
if (guid == GetPlayer()->GetGUID())
|
||||
{
|
||||
TC_LOG_ERROR("network", "WorldSession::HandleGroupUninviteGuidOpcode: leader %s(%d) tried to uninvite himself from the group.",
|
||||
GetPlayer()->GetName().c_str(), GetPlayer()->GetGUID().GetCounter());
|
||||
return;
|
||||
}
|
||||
|
||||
PartyResult res = GetPlayer()->CanUninviteFromGroup(guid);
|
||||
if (res != ERR_PARTY_RESULT_OK)
|
||||
{
|
||||
SendPartyResult(PARTY_OP_UNINVITE, "", res);
|
||||
return;
|
||||
}
|
||||
|
||||
Group* grp = GetPlayer()->GetGroup();
|
||||
// grp is checked already above in CanUninviteFromGroup()
|
||||
ASSERT(grp);
|
||||
|
||||
if (grp->IsMember(guid))
|
||||
{
|
||||
Player::RemoveFromGroup(grp, guid, GROUP_REMOVEMETHOD_KICK, GetPlayer()->GetGUID(), reason.c_str());
|
||||
return;
|
||||
}
|
||||
|
||||
if (Player* player = grp->GetInvited(guid))
|
||||
{
|
||||
player->UninviteFromGroup();
|
||||
return;
|
||||
}
|
||||
|
||||
SendPartyResult(PARTY_OP_UNINVITE, "", ERR_TARGET_NOT_IN_GROUP_S);
|
||||
}
|
||||
|
||||
void WorldSession::HandleGroupUninviteOpcode(WorldPacket& recvData)
|
||||
{
|
||||
TC_LOG_DEBUG("network", "WORLD: Received CMSG_GROUP_UNINVITE");
|
||||
|
||||
std::string membername;
|
||||
recvData >> membername;
|
||||
|
||||
// player not found
|
||||
if (!normalizePlayerName(membername))
|
||||
return;
|
||||
|
||||
// can't uninvite yourself
|
||||
if (GetPlayer()->GetName() == membername)
|
||||
{
|
||||
TC_LOG_ERROR("network", "WorldSession::HandleGroupUninviteOpcode: leader %s(%d) tried to uninvite himself from the group.",
|
||||
GetPlayer()->GetName().c_str(), GetPlayer()->GetGUID().GetCounter());
|
||||
return;
|
||||
}
|
||||
|
||||
PartyResult res = GetPlayer()->CanUninviteFromGroup();
|
||||
if (res != ERR_PARTY_RESULT_OK)
|
||||
{
|
||||
SendPartyResult(PARTY_OP_UNINVITE, "", res);
|
||||
return;
|
||||
}
|
||||
|
||||
Group* grp = GetPlayer()->GetGroup();
|
||||
if (!grp)
|
||||
return;
|
||||
|
||||
if (ObjectGuid guid = grp->GetMemberGUID(membername))
|
||||
{
|
||||
Player::RemoveFromGroup(grp, guid, GROUP_REMOVEMETHOD_KICK, GetPlayer()->GetGUID());
|
||||
return;
|
||||
}
|
||||
|
||||
if (Player* player = grp->GetInvited(membername))
|
||||
{
|
||||
player->UninviteFromGroup();
|
||||
return;
|
||||
}
|
||||
|
||||
SendPartyResult(PARTY_OP_UNINVITE, membername, ERR_TARGET_NOT_IN_GROUP_S);
|
||||
}
|
||||
|
||||
void WorldSession::HandleGroupSetLeaderOpcode(WorldPacket& recvData)
|
||||
{
|
||||
TC_LOG_DEBUG("network", "WORLD: Received CMSG_GROUP_SET_LEADER");
|
||||
|
||||
ObjectGuid guid;
|
||||
recvData >> guid;
|
||||
|
||||
Player* player = ObjectAccessor::FindConnectedPlayer(guid);
|
||||
Group* group = GetPlayer()->GetGroup();
|
||||
|
||||
if (!group || !player)
|
||||
return;
|
||||
|
||||
if (!group->IsLeader(GetPlayer()->GetGUID()) || player->GetGroup() != group)
|
||||
return;
|
||||
|
||||
// Everything's fine, accepted.
|
||||
group->ChangeLeader(guid);
|
||||
group->SendUpdate();
|
||||
}
|
||||
|
||||
void WorldSession::HandleSetRoleOpcode(WorldPackets::Party::SetRole& packet)
|
||||
{
|
||||
WorldPackets::Party::RoleChangedInform roleChangedInform;
|
||||
|
||||
Group* group = GetPlayer()->GetGroup();
|
||||
uint8 oldRole = group ? group->GetLfgRoles(packet.ChangedUnit) : 0;
|
||||
if (oldRole == packet.Role)
|
||||
return;
|
||||
|
||||
roleChangedInform.From = GetPlayer()->GetGUID();
|
||||
roleChangedInform.ChangedUnit = packet.ChangedUnit;
|
||||
roleChangedInform.OldRole = oldRole;
|
||||
roleChangedInform.NewRole = packet.Role;
|
||||
|
||||
if (group)
|
||||
{
|
||||
group->BroadcastPacket(roleChangedInform.Write(), false);
|
||||
group->SetLfgRoles(packet.ChangedUnit, packet.Role);
|
||||
}
|
||||
else
|
||||
SendPacket(roleChangedInform.Write());
|
||||
}
|
||||
|
||||
void WorldSession::HandleGroupDisbandOpcode(WorldPacket& /*recvData*/)
|
||||
{
|
||||
TC_LOG_DEBUG("network", "WORLD: Received CMSG_GROUP_DISBAND");
|
||||
|
||||
Group* grp = GetPlayer()->GetGroup();
|
||||
Group* grpInvite = GetPlayer()->GetGroupInvite();
|
||||
if (!grp && !grpInvite)
|
||||
return;
|
||||
|
||||
if (_player->InBattleground())
|
||||
{
|
||||
SendPartyResult(PARTY_OP_INVITE, "", ERR_INVITE_RESTRICTED);
|
||||
return;
|
||||
}
|
||||
|
||||
/** error handling **/
|
||||
/********************/
|
||||
|
||||
// everything's fine, do it
|
||||
if (grp)
|
||||
{
|
||||
SendPartyResult(PARTY_OP_LEAVE, GetPlayer()->GetName(), ERR_PARTY_RESULT_OK);
|
||||
GetPlayer()->RemoveFromGroup(GROUP_REMOVEMETHOD_LEAVE);
|
||||
}
|
||||
else if (grpInvite && grpInvite->GetLeaderGUID() == GetPlayer()->GetGUID())
|
||||
{ // pending group creation being cancelled
|
||||
SendPartyResult(PARTY_OP_LEAVE, GetPlayer()->GetName(), ERR_PARTY_RESULT_OK);
|
||||
grpInvite->Disband();
|
||||
}
|
||||
}
|
||||
|
||||
void WorldSession::HandleLootMethodOpcode(WorldPacket& recvData)
|
||||
{
|
||||
TC_LOG_DEBUG("network", "WORLD: Received CMSG_LOOT_METHOD");
|
||||
|
||||
uint32 lootMethod;
|
||||
ObjectGuid lootMaster;
|
||||
uint32 lootThreshold;
|
||||
recvData >> lootMethod >> lootMaster >> lootThreshold;
|
||||
|
||||
Group* group = GetPlayer()->GetGroup();
|
||||
if (!group)
|
||||
return;
|
||||
|
||||
/** error handling **/
|
||||
if (!group->IsLeader(GetPlayer()->GetGUID()))
|
||||
return;
|
||||
|
||||
if (group->isLFGGroup())
|
||||
return;
|
||||
|
||||
if (lootMethod > NEED_BEFORE_GREED)
|
||||
return;
|
||||
|
||||
if (lootThreshold < ITEM_QUALITY_UNCOMMON || lootThreshold > ITEM_QUALITY_ARTIFACT)
|
||||
return;
|
||||
|
||||
if (lootMethod == MASTER_LOOT && !group->IsMember(lootMaster))
|
||||
return;
|
||||
/********************/
|
||||
|
||||
// everything's fine, do it
|
||||
group->SetLootMethod((LootMethod)lootMethod);
|
||||
group->SetMasterLooterGuid(lootMaster);
|
||||
group->SetLootThreshold((ItemQualities)lootThreshold);
|
||||
group->SendUpdate();
|
||||
}
|
||||
|
||||
void WorldSession::HandleLootRoll(WorldPacket& recvData)
|
||||
{
|
||||
ObjectGuid guid;
|
||||
uint32 itemSlot;
|
||||
uint8 rollType;
|
||||
recvData >> guid; // guid of the item rolled
|
||||
recvData >> itemSlot;
|
||||
recvData >> rollType; // 0: pass, 1: need, 2: greed
|
||||
|
||||
Group* group = GetPlayer()->GetGroup();
|
||||
if (!group)
|
||||
return;
|
||||
|
||||
group->CountRollVote(GetPlayer()->GetGUID(), guid, rollType);
|
||||
|
||||
switch (rollType)
|
||||
{
|
||||
case ROLL_NEED:
|
||||
GetPlayer()->UpdateAchievementCriteria(ACHIEVEMENT_CRITERIA_TYPE_ROLL_NEED, 1);
|
||||
break;
|
||||
case ROLL_GREED:
|
||||
GetPlayer()->UpdateAchievementCriteria(ACHIEVEMENT_CRITERIA_TYPE_ROLL_GREED, 1);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void WorldSession::HandleMinimapPingOpcode(WorldPacket& recvData)
|
||||
{
|
||||
TC_LOG_DEBUG("network", "WORLD: Received MSG_MINIMAP_PING");
|
||||
|
||||
if (!GetPlayer()->GetGroup())
|
||||
return;
|
||||
|
||||
float x, y;
|
||||
recvData >> x;
|
||||
recvData >> y;
|
||||
|
||||
//TC_LOG_DEBUG("misc", "Received opcode MSG_MINIMAP_PING X: %f, Y: %f", x, y);
|
||||
|
||||
/** error handling **/
|
||||
/********************/
|
||||
|
||||
// everything's fine, do it
|
||||
WorldPacket data(MSG_MINIMAP_PING, (8+4+4));
|
||||
data << uint64(GetPlayer()->GetGUID());
|
||||
data << float(x);
|
||||
data << float(y);
|
||||
GetPlayer()->GetGroup()->BroadcastPacket(&data, true, -1, GetPlayer()->GetGUID());
|
||||
}
|
||||
|
||||
void WorldSession::HandleRandomRollOpcode(WorldPacket& recvData)
|
||||
{
|
||||
uint32 minimum, maximum;
|
||||
recvData >> minimum;
|
||||
recvData >> maximum;
|
||||
|
||||
/** error handling **/
|
||||
if (minimum > maximum || maximum > 10000) // < 32768 for urand call
|
||||
return;
|
||||
/********************/
|
||||
|
||||
// everything's fine, do it
|
||||
GetPlayer()->DoRandomRoll(minimum, maximum);
|
||||
}
|
||||
|
||||
void WorldSession::HandleRaidTargetUpdateOpcode(WorldPacket& recvData)
|
||||
{
|
||||
TC_LOG_DEBUG("network", "WORLD: Received MSG_RAID_TARGET_UPDATE");
|
||||
|
||||
Group* group = GetPlayer()->GetGroup();
|
||||
if (!group)
|
||||
return;
|
||||
|
||||
uint8 x;
|
||||
recvData >> x;
|
||||
|
||||
/** error handling **/
|
||||
/********************/
|
||||
|
||||
// everything's fine, do it
|
||||
if (x == 0xFF) // target icon request
|
||||
group->SendTargetIconList(this);
|
||||
else // target icon update
|
||||
{
|
||||
if (group->isRaidGroup() && !group->IsLeader(GetPlayer()->GetGUID()) && !group->IsAssistant(GetPlayer()->GetGUID()))
|
||||
return;
|
||||
|
||||
ObjectGuid guid;
|
||||
recvData >> guid;
|
||||
|
||||
if (guid.IsPlayer())
|
||||
{
|
||||
Player* target = ObjectAccessor::FindConnectedPlayer(guid);
|
||||
|
||||
if (!target || target->IsHostileTo(GetPlayer()))
|
||||
return;
|
||||
}
|
||||
|
||||
group->SetTargetIcon(x, _player->GetGUID(), guid);
|
||||
}
|
||||
}
|
||||
|
||||
void WorldSession::HandleGroupRaidConvertOpcode(WorldPacket& recvData)
|
||||
{
|
||||
TC_LOG_DEBUG("network", "WORLD: Received CMSG_GROUP_RAID_CONVERT");
|
||||
|
||||
Group* group = GetPlayer()->GetGroup();
|
||||
if (!group)
|
||||
return;
|
||||
|
||||
if (_player->InBattleground() || group->isLFGGroup())
|
||||
return;
|
||||
|
||||
// error handling
|
||||
if (!group->IsLeader(GetPlayer()->GetGUID()) || group->GetMembersCount() < 2)
|
||||
return;
|
||||
|
||||
// everything's fine, do it (is it 0 (PARTY_OP_INVITE) correct code)
|
||||
SendPartyResult(PARTY_OP_INVITE, "", ERR_PARTY_RESULT_OK);
|
||||
|
||||
// New 4.x: it is now possible to convert a raid to a group if member count is 5 or less
|
||||
|
||||
bool toRaid;
|
||||
recvData >> toRaid;
|
||||
|
||||
if (toRaid)
|
||||
group->ConvertToRaid();
|
||||
else
|
||||
group->ConvertToGroup();
|
||||
}
|
||||
|
||||
void WorldSession::HandleGroupRequestJoinUpdates(WorldPacket& /*recvData*/)
|
||||
{
|
||||
Group* group = GetPlayer()->GetGroup();
|
||||
if (!group)
|
||||
return;
|
||||
|
||||
WorldPacket data(SMSG_REAL_GROUP_UPDATE, 1 + 4 + 8);
|
||||
data << uint8(group->GetGroupFlags());
|
||||
data << uint32(group->GetMembersCount() - 1);
|
||||
data << uint64(group->GetLeaderGUID());
|
||||
SendPacket(&data);
|
||||
}
|
||||
|
||||
void WorldSession::HandleGroupChangeSubGroupOpcode(WorldPacket& recvData)
|
||||
{
|
||||
TC_LOG_DEBUG("network", "WORLD: Received CMSG_GROUP_CHANGE_SUB_GROUP");
|
||||
|
||||
// we will get correct pointer for group here, so we don't have to check if group is BG raid
|
||||
Group* group = GetPlayer()->GetGroup();
|
||||
if (!group)
|
||||
return;
|
||||
|
||||
std::string name;
|
||||
uint8 groupNr;
|
||||
recvData >> name;
|
||||
recvData >> groupNr;
|
||||
|
||||
if (!normalizePlayerName(name))
|
||||
return;
|
||||
|
||||
if (groupNr >= MAX_RAID_SUBGROUPS)
|
||||
return;
|
||||
|
||||
ObjectGuid senderGuid = GetPlayer()->GetGUID();
|
||||
if (!group->IsLeader(senderGuid) && !group->IsAssistant(senderGuid))
|
||||
return;
|
||||
|
||||
if (!group->HasFreeSlotSubGroup(groupNr))
|
||||
return;
|
||||
|
||||
ObjectGuid guid;
|
||||
if (Player* movedPlayer = ObjectAccessor::FindConnectedPlayerByName(name))
|
||||
guid = movedPlayer->GetGUID();
|
||||
else
|
||||
guid = sCharacterCache->GetCharacterGuidByName(name);
|
||||
|
||||
if (guid.IsEmpty())
|
||||
return;
|
||||
|
||||
group->ChangeMembersGroup(guid, groupNr);
|
||||
}
|
||||
|
||||
void WorldSession::HandleGroupSwapSubGroupOpcode(WorldPacket& recvData)
|
||||
{
|
||||
TC_LOG_DEBUG("network", "WORLD: Received CMSG_GROUP_SWAP_SUB_GROUP");
|
||||
std::string name1;
|
||||
std::string name2;
|
||||
|
||||
recvData >> name1;
|
||||
recvData >> name2;
|
||||
}
|
||||
|
||||
void WorldSession::HandleGroupAssistantLeaderOpcode(WorldPacket& recvData)
|
||||
{
|
||||
TC_LOG_DEBUG("network", "WORLD: Received CMSG_GROUP_ASSISTANT_LEADER");
|
||||
|
||||
Group* group = GetPlayer()->GetGroup();
|
||||
if (!group)
|
||||
return;
|
||||
|
||||
if (!group->IsLeader(GetPlayer()->GetGUID()))
|
||||
return;
|
||||
|
||||
ObjectGuid guid;
|
||||
bool apply;
|
||||
recvData >> guid;
|
||||
recvData >> apply;
|
||||
|
||||
group->SetGroupMemberFlag(guid, apply, MEMBER_FLAG_ASSISTANT);
|
||||
}
|
||||
|
||||
void WorldSession::HandlePartyAssignmentOpcode(WorldPacket& recvData)
|
||||
{
|
||||
TC_LOG_DEBUG("network", "WORLD: Received MSG_PARTY_ASSIGNMENT");
|
||||
|
||||
Group* group = GetPlayer()->GetGroup();
|
||||
if (!group)
|
||||
return;
|
||||
|
||||
ObjectGuid senderGuid = GetPlayer()->GetGUID();
|
||||
if (!group->IsLeader(senderGuid) && !group->IsAssistant(senderGuid))
|
||||
return;
|
||||
|
||||
uint8 assignment;
|
||||
bool apply;
|
||||
ObjectGuid guid;
|
||||
recvData >> assignment >> apply;
|
||||
recvData >> guid;
|
||||
|
||||
switch (assignment)
|
||||
{
|
||||
case GROUP_ASSIGN_MAINASSIST:
|
||||
group->RemoveUniqueGroupMemberFlag(MEMBER_FLAG_MAINASSIST);
|
||||
group->SetGroupMemberFlag(guid, apply, MEMBER_FLAG_MAINASSIST);
|
||||
break;
|
||||
case GROUP_ASSIGN_MAINTANK:
|
||||
group->RemoveUniqueGroupMemberFlag(MEMBER_FLAG_MAINTANK); // Remove main assist flag from current if any.
|
||||
group->SetGroupMemberFlag(guid, apply, MEMBER_FLAG_MAINTANK);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
group->SendUpdate();
|
||||
}
|
||||
|
||||
void WorldSession::HandleRaidReadyCheckOpcode(WorldPacket& recvData)
|
||||
{
|
||||
TC_LOG_DEBUG("network", "WORLD: Received MSG_RAID_READY_CHECK");
|
||||
|
||||
Group* group = GetPlayer()->GetGroup();
|
||||
if (!group)
|
||||
return;
|
||||
|
||||
if (recvData.empty()) // request
|
||||
{
|
||||
/** error handling **/
|
||||
if (!group->IsLeader(GetPlayer()->GetGUID()) && !group->IsAssistant(GetPlayer()->GetGUID()))
|
||||
return;
|
||||
/********************/
|
||||
|
||||
// everything's fine, do it
|
||||
WorldPacket data(MSG_RAID_READY_CHECK, 8);
|
||||
data << GetPlayer()->GetGUID();
|
||||
group->BroadcastPacket(&data, false, -1);
|
||||
|
||||
group->OfflineReadyCheck();
|
||||
}
|
||||
else // answer
|
||||
{
|
||||
uint8 state;
|
||||
recvData >> state;
|
||||
|
||||
// everything's fine, do it
|
||||
WorldPacket data(MSG_RAID_READY_CHECK_CONFIRM, 9);
|
||||
data << uint64(GetPlayer()->GetGUID());
|
||||
data << uint8(state);
|
||||
group->BroadcastReadyCheck(&data);
|
||||
}
|
||||
}
|
||||
|
||||
void WorldSession::HandleRaidReadyCheckFinishedOpcode(WorldPacket& /*recvData*/)
|
||||
{
|
||||
//Group* group = GetPlayer()->GetGroup();
|
||||
//if (!group)
|
||||
// return;
|
||||
|
||||
//if (!group->IsLeader(GetPlayer()->GetGUID()) && !group->IsAssistant(GetPlayer()->GetGUID()))
|
||||
// return;
|
||||
|
||||
// Is any reaction need?
|
||||
}
|
||||
|
||||
/*this procedure handles clients CMSG_REQUEST_PARTY_MEMBER_STATS request*/
|
||||
void WorldSession::HandleRequestPartyMemberStatsOpcode(WorldPacket& recvData)
|
||||
{
|
||||
TC_LOG_DEBUG("network", "WORLD: Received CMSG_REQUEST_PARTY_MEMBER_STATS");
|
||||
ObjectGuid Guid;
|
||||
recvData >> Guid;
|
||||
|
||||
Player* player = ObjectAccessor::FindConnectedPlayer(Guid);
|
||||
if (!player)
|
||||
{
|
||||
WorldPackets::Party::PartyMemberState packet(true);
|
||||
packet.MemberGuid = Guid;
|
||||
packet.ChangeMask = GROUP_UPDATE_FLAG_STATUS;
|
||||
packet.MemberStats.Status = MEMBER_STATUS_OFFLINE;
|
||||
SendPacket(packet.Write());
|
||||
return;
|
||||
}
|
||||
|
||||
WorldPackets::Party::PartyMemberState packet(true);
|
||||
packet.Initialize(player);
|
||||
SendPacket(packet.Write());
|
||||
}
|
||||
|
||||
void WorldSession::HandleRequestRaidInfoOpcode(WorldPacket& /*recvData*/)
|
||||
{
|
||||
// every time the player checks the character screen
|
||||
_player->SendRaidInfo();
|
||||
}
|
||||
|
||||
void WorldSession::HandleOptOutOfLootOpcode(WorldPacket& recvData)
|
||||
{
|
||||
TC_LOG_DEBUG("network", "WORLD: Received CMSG_OPT_OUT_OF_LOOT");
|
||||
|
||||
bool passOnLoot;
|
||||
recvData >> passOnLoot; // 1 always pass, 0 do not pass
|
||||
|
||||
// ignore if player not loaded
|
||||
if (!GetPlayer()) // needed because STATUS_AUTHED
|
||||
{
|
||||
if (passOnLoot)
|
||||
TC_LOG_ERROR("network", "CMSG_OPT_OUT_OF_LOOT value<>0 for not-loaded character!");
|
||||
return;
|
||||
}
|
||||
|
||||
GetPlayer()->SetPassOnGroupLoot(passOnLoot != 0);
|
||||
}
|
||||
|
||||
void WorldSession::HandleRolePollBeginOpcode(WorldPacket& recvData)
|
||||
{
|
||||
TC_LOG_DEBUG("network", "WORLD: Received CMSG_ROLE_POLL_BEGIN");
|
||||
|
||||
Group* group = GetPlayer()->GetGroup();
|
||||
if (!group)
|
||||
return;
|
||||
|
||||
if (recvData.empty())
|
||||
{
|
||||
if (!group->IsLeader(GetPlayer()->GetGUID()) && !group->IsAssistant(GetPlayer()->GetGUID()))
|
||||
return;
|
||||
|
||||
ObjectGuid guid = GetPlayer()->GetGUID();
|
||||
|
||||
WorldPacket data(SMSG_ROLE_POLL_BEGIN, 8);
|
||||
data.WriteBit(guid[1]);
|
||||
data.WriteBit(guid[5]);
|
||||
data.WriteBit(guid[7]);
|
||||
data.WriteBit(guid[3]);
|
||||
data.WriteBit(guid[2]);
|
||||
data.WriteBit(guid[4]);
|
||||
data.WriteBit(guid[0]);
|
||||
data.WriteBit(guid[6]);
|
||||
data.WriteByteSeq(guid[4]);
|
||||
data.WriteByteSeq(guid[7]);
|
||||
data.WriteByteSeq(guid[0]);
|
||||
data.WriteByteSeq(guid[5]);
|
||||
data.WriteByteSeq(guid[1]);
|
||||
data.WriteByteSeq(guid[6]);
|
||||
data.WriteByteSeq(guid[2]);
|
||||
data.WriteByteSeq(guid[3]);
|
||||
|
||||
GetPlayer()->GetGroup()->BroadcastPacket(&data, true);
|
||||
}
|
||||
}
|
||||
|
||||
void WorldSession::HandleSetEveryoneIsAssistant(WorldPackets::Party::SetEveryoneIsAssistant& packet)
|
||||
{
|
||||
Group* group = GetPlayer()->GetGroup();
|
||||
if (!group)
|
||||
return;
|
||||
|
||||
if (!group->IsLeader(GetPlayer()->GetGUID()))
|
||||
return;
|
||||
|
||||
group->SetEveryoneIsAssistant(packet.EveryoneIsAssistant);
|
||||
}
|
||||
@@ -23,7 +23,9 @@
|
||||
|
||||
void GenericMovementGenerator::Initialize(Unit* /*owner*/)
|
||||
{
|
||||
_duration.Reset(_splineInit.Launch());
|
||||
int32 duration = _splineInit.Launch();
|
||||
_launched = duration > 0;
|
||||
_duration.Reset(duration);
|
||||
}
|
||||
|
||||
bool GenericMovementGenerator::Update(Unit* owner, uint32 diff)
|
||||
@@ -40,7 +42,8 @@ bool GenericMovementGenerator::Update(Unit* owner, uint32 diff)
|
||||
|
||||
void GenericMovementGenerator::Finalize(Unit* owner)
|
||||
{
|
||||
MovementInform(owner);
|
||||
if (_launched)
|
||||
MovementInform(owner);
|
||||
}
|
||||
|
||||
void GenericMovementGenerator::MovementInform(Unit* owner)
|
||||
|
||||
+51
@@ -0,0 +1,51 @@
|
||||
/*
|
||||
* This file is part of the TrinityCore 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 "GenericMovementGenerator.h"
|
||||
#include "Creature.h"
|
||||
#include "CreatureAI.h"
|
||||
#include "MoveSpline.h"
|
||||
#include "Unit.h"
|
||||
|
||||
void GenericMovementGenerator::Initialize(Unit* /*owner*/)
|
||||
{
|
||||
_duration.Reset(_splineInit.Launch());
|
||||
}
|
||||
|
||||
bool GenericMovementGenerator::Update(Unit* owner, uint32 diff)
|
||||
{
|
||||
// Cyclic splines never expire, so update the duration only if it's not cyclic
|
||||
if (!owner->movespline->isCyclic())
|
||||
_duration.Update(diff);
|
||||
|
||||
if (_duration.Passed())
|
||||
return false;
|
||||
|
||||
return !owner->movespline->Finalized();
|
||||
}
|
||||
|
||||
void GenericMovementGenerator::Finalize(Unit* owner)
|
||||
{
|
||||
MovementInform(owner);
|
||||
}
|
||||
|
||||
void GenericMovementGenerator::MovementInform(Unit* owner)
|
||||
{
|
||||
if (Creature* creature = owner->ToCreature())
|
||||
if (creature->AI())
|
||||
creature->AI()->MovementInform(_type, _pointId);
|
||||
}
|
||||
@@ -29,7 +29,7 @@ enum MovementGeneratorType : uint8;
|
||||
class GenericMovementGenerator : public MovementGenerator
|
||||
{
|
||||
public:
|
||||
explicit GenericMovementGenerator(Movement::MoveSplineInit&& splineInit, MovementGeneratorType type, uint32 id) : _splineInit(std::move(splineInit)), _type(type), _pointId(id), _duration(0) { }
|
||||
explicit GenericMovementGenerator(Movement::MoveSplineInit&& splineInit, MovementGeneratorType type, uint32 id) : _splineInit(std::move(splineInit)), _type(type), _pointId(id), _duration(0), _launched(false) { }
|
||||
|
||||
void Initialize(Unit*) override;
|
||||
void Finalize(Unit*) override;
|
||||
@@ -44,6 +44,7 @@ class GenericMovementGenerator : public MovementGenerator
|
||||
MovementGeneratorType _type;
|
||||
uint32 _pointId;
|
||||
TimeTrackerSmall _duration;
|
||||
bool _launched;
|
||||
};
|
||||
|
||||
#endif
|
||||
+49
@@ -0,0 +1,49 @@
|
||||
/*
|
||||
* This file is part of the TrinityCore 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/>.
|
||||
*/
|
||||
|
||||
#ifndef TRINITY_GENERICMOVEMENTGENERATOR_H
|
||||
#define TRINITY_GENERICMOVEMENTGENERATOR_H
|
||||
|
||||
#include "MovementGenerator.h"
|
||||
#include "MoveSplineInit.h"
|
||||
#include "Timer.h"
|
||||
|
||||
class Unit;
|
||||
|
||||
enum MovementGeneratorType : uint8;
|
||||
|
||||
class GenericMovementGenerator : public MovementGenerator
|
||||
{
|
||||
public:
|
||||
explicit GenericMovementGenerator(Movement::MoveSplineInit&& splineInit, MovementGeneratorType type, uint32 id) : _splineInit(std::move(splineInit)), _type(type), _pointId(id), _duration(0) { }
|
||||
|
||||
void Initialize(Unit*) override;
|
||||
void Finalize(Unit*) override;
|
||||
void Reset(Unit*) override { }
|
||||
bool Update(Unit*, uint32) override;
|
||||
MovementGeneratorType GetMovementGeneratorType() const override { return _type; }
|
||||
|
||||
private:
|
||||
void MovementInform(Unit*);
|
||||
|
||||
Movement::MoveSplineInit _splineInit;
|
||||
MovementGeneratorType _type;
|
||||
uint32 _pointId;
|
||||
TimeTrackerSmall _duration;
|
||||
};
|
||||
|
||||
#endif
|
||||
@@ -527,8 +527,10 @@ int32 SpellEffectInfo::CalcValue(WorldObject const* caster /*= nullptr*/, int32
|
||||
{
|
||||
double comboDamage = CalcPointsPerResource(casterUnit);
|
||||
if (comboDamage != 0.0)
|
||||
if (uint8 comboPoints = casterUnit->GetGameClientMovingMe()->GetBasePlayer()->GetComboPoints())
|
||||
value += comboDamage * comboPoints;
|
||||
if (GameClient* gameClient = casterUnit->GetGameClientMovingMe())
|
||||
if (Player* player = gameClient->GetBasePlayer())
|
||||
if (uint8 comboPoints = player->GetComboPoints())
|
||||
value += comboDamage * comboPoints;
|
||||
}
|
||||
|
||||
if (_spellInfo->HasAttribute(SPELL_ATTR8_MASTERY_AFFECTS_POINTS))
|
||||
@@ -1933,6 +1935,12 @@ SpellCastResult SpellInfo::CheckLocation(uint32 map_id, uint32 zone_id, uint32 a
|
||||
|
||||
SpellCastResult SpellInfo::CheckTarget(WorldObject const* caster, WorldObject const* target, bool implicit /*= true*/) const
|
||||
{
|
||||
// VIANO_AURA_DEBUG_CHECKTARGET_V1
|
||||
Unit const* vianoTarget = target ? target->ToUnit() : nullptr;
|
||||
if (Id == 465 && vianoTarget && vianoTarget->IsVianoAdventurer())
|
||||
TC_LOG_INFO("scripts", "[VianoAuraDebug] stage=CheckTarget spell=%u target=%s controlled=%u",
|
||||
Id, vianoTarget->GetGUID().ToString().c_str(), vianoTarget->IsControlledByPlayer() ? 1u : 0u);
|
||||
|
||||
if (HasAttribute(SPELL_ATTR1_EXCLUDE_CASTER) && caster == target)
|
||||
return SPELL_FAILED_BAD_TARGETS;
|
||||
|
||||
@@ -2026,7 +2034,12 @@ SpellCastResult SpellInfo::CheckTarget(WorldObject const* caster, WorldObject co
|
||||
if (HasAttribute(SPELL_ATTR3_ONLY_ON_PLAYER))
|
||||
return SPELL_FAILED_TARGET_NOT_PLAYER;
|
||||
|
||||
if (HasAttribute(SPELL_ATTR5_NOT_ON_PLAYER_CONTROLLED_NPC) && unitTarget->IsControlledByPlayer())
|
||||
// VIANO_AURA_PLAYER_CONTROLLED_EXCEPTION_V1
|
||||
// Viano adventurers keep PLAYER_CONTROLLED for direct helpful targeting,
|
||||
// but are intentionally eligible for group/raid buffs such as paladin auras.
|
||||
if (HasAttribute(SPELL_ATTR5_NOT_ON_PLAYER_CONTROLLED_NPC)
|
||||
&& unitTarget->IsControlledByPlayer()
|
||||
&& !unitTarget->IsVianoAdventurer())
|
||||
return SPELL_FAILED_TARGET_IS_PLAYER_CONTROLLED;
|
||||
}
|
||||
else if (HasAttribute(SPELL_ATTR5_NOT_ON_PLAYER))
|
||||
|
||||
File diff suppressed because it is too large.
Load diff
+4522
File diff suppressed because it is too large.
Load diff
File diff suppressed because it is too large.
Load diff
File diff suppressed because it is too large.
Load diff
-34
@@ -1,34 +0,0 @@
|
||||
/*
|
||||
* This file is part of the TrinityCore 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/>.
|
||||
*/
|
||||
|
||||
// This is where scripts' loading functions should be declared:
|
||||
|
||||
|
||||
// VIANO_ADVENTURERS
|
||||
void AddSC_viano_adventurers();
|
||||
|
||||
// Viano Server diagnostics
|
||||
void AddSC_vcore_diagnostics();
|
||||
|
||||
// The name of this function should match:
|
||||
// void Add${NameOfDirectory}Scripts()
|
||||
void AddCustomScripts()
|
||||
{
|
||||
AddSC_viano_adventurers();
|
||||
AddSC_vcore_diagnostics();
|
||||
|
||||
}
|
||||
File diff suppressed because it is too large.
Load diff
@@ -11,6 +11,13 @@
|
||||
#include "TemporarySummon.h"
|
||||
#include "MotionMaster.h"
|
||||
#include "Log.h"
|
||||
#include "SpellInfo.h"
|
||||
#include "SpellAuras.h"
|
||||
#include <string>
|
||||
#include <list>
|
||||
#include "LFGMgr.h"
|
||||
#include "LFG.h"
|
||||
#include "ObjectMgr.h"
|
||||
#include <algorithm>
|
||||
#include <vector>
|
||||
|
||||
@@ -22,6 +29,102 @@ namespace VianoAdventurers
|
||||
static constexpr uint32 UPDATE_INTERVAL = 500;
|
||||
static constexpr uint32 DIAGNOSTIC_INTERVAL = 2000;
|
||||
|
||||
|
||||
// VIANO_FORMATION_V1
|
||||
static bool IsVianoTankUnit(Unit* unit)
|
||||
{
|
||||
Creature* creature = unit ? unit->ToCreature() : nullptr;
|
||||
if (!creature || !creature->IsVianoAdventurer())
|
||||
return false;
|
||||
|
||||
CreatureTemplate const* cinfo = creature->GetCreatureTemplate();
|
||||
if (!cinfo)
|
||||
return false;
|
||||
|
||||
std::string const scriptName = sObjectMgr->GetScriptName(cinfo->ScriptID);
|
||||
return scriptName.rfind("npc_horde_adventurer_tank", 0) == 0
|
||||
|| scriptName.rfind("npc_alliance_adventurer_tank", 0) == 0;
|
||||
}
|
||||
|
||||
static void GetVianoFollowSlot(Creature* bot, Player* owner, float& distance, float& angle)
|
||||
{
|
||||
distance = FOLLOW_DISTANCE;
|
||||
angle = FOLLOW_ANGLE;
|
||||
|
||||
if (!bot || !owner)
|
||||
return;
|
||||
|
||||
std::list<Unit*> groupUnits;
|
||||
owner->GetPartyMembers(groupUnits);
|
||||
|
||||
std::vector<Creature*> bots;
|
||||
for (Unit* unit : groupUnits)
|
||||
{
|
||||
Creature* creature = unit ? unit->ToCreature() : nullptr;
|
||||
if (!creature || !creature->IsVianoAdventurer())
|
||||
continue;
|
||||
if (!creature->IsAlive() || !creature->IsInMap(owner))
|
||||
continue;
|
||||
|
||||
bots.push_back(creature);
|
||||
}
|
||||
|
||||
std::sort(bots.begin(), bots.end(), [](Creature const* left, Creature const* right)
|
||||
{
|
||||
return left->GetGUID().GetRawValue() < right->GetGUID().GetRawValue();
|
||||
});
|
||||
|
||||
size_t slot = 0;
|
||||
bool found = false;
|
||||
for (size_t i = 0; i < bots.size(); ++i)
|
||||
{
|
||||
if (bots[i] == bot)
|
||||
{
|
||||
slot = i;
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!found)
|
||||
return;
|
||||
|
||||
struct FollowSlot
|
||||
{
|
||||
float distance;
|
||||
float angle;
|
||||
};
|
||||
|
||||
static FollowSlot const baseSlots[] =
|
||||
{
|
||||
{ 2.7f, 3.14159265f },
|
||||
{ 3.1f, 2.52f },
|
||||
{ 3.1f, 3.76f },
|
||||
{ 4.0f, 2.25f },
|
||||
{ 4.0f, 4.03f },
|
||||
{ 4.4f, 2.70f },
|
||||
{ 4.4f, 3.58f },
|
||||
{ 4.8f, 3.14159265f }
|
||||
};
|
||||
|
||||
if (slot < sizeof(baseSlots) / sizeof(baseSlots[0]))
|
||||
{
|
||||
distance = baseSlots[slot].distance;
|
||||
angle = baseSlots[slot].angle;
|
||||
return;
|
||||
}
|
||||
|
||||
// Unlimited development-mode fallback: additional bots form wider rows
|
||||
// instead of being stacked on one point.
|
||||
static float const offsets[] = { -0.95f, -0.48f, 0.0f, 0.48f, 0.95f };
|
||||
size_t extra = slot - (sizeof(baseSlots) / sizeof(baseSlots[0]));
|
||||
size_t row = extra / 5;
|
||||
size_t column = extra % 5;
|
||||
|
||||
distance = 5.4f + float(row) * 1.25f;
|
||||
angle = 3.14159265f + offsets[column];
|
||||
}
|
||||
|
||||
// VIANO_DD_PROFILE_V1_5
|
||||
enum AdventurerSpells : uint32
|
||||
{
|
||||
@@ -66,7 +169,10 @@ namespace VianoAdventurers
|
||||
if (!owner)
|
||||
return;
|
||||
|
||||
me->GetMotionMaster()->MoveFollow(owner, FOLLOW_DISTANCE, FOLLOW_ANGLE);
|
||||
float followDistance = FOLLOW_DISTANCE;
|
||||
float followAngle = FOLLOW_ANGLE;
|
||||
GetVianoFollowSlot(me, owner, followDistance, followAngle);
|
||||
me->GetMotionMaster()->MoveFollow(owner, followDistance, followAngle);
|
||||
}
|
||||
|
||||
void ResetCombatEvents()
|
||||
@@ -373,39 +479,156 @@ namespace VianoAdventurers
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
Unit* SelectAssistTarget(Player* owner)
|
||||
Unit* SelectAssistTarget(Player* owner)
|
||||
{
|
||||
if (!owner)
|
||||
return nullptr;
|
||||
|
||||
// 1. An active owner victim is an explicit target instruction.
|
||||
if (Unit* target = owner->GetVictim())
|
||||
if (IsValidCandidate(target))
|
||||
return target;
|
||||
|
||||
// During an already running fight, an explicitly selected living
|
||||
// hostile target may also be treated as an instruction. Merely
|
||||
// selecting an enemy out of combat still must not pull it.
|
||||
if (owner->IsInCombat())
|
||||
auto isCombatRelevant = [](Unit* target) -> bool
|
||||
{
|
||||
if (Unit* target = owner->GetSelectedUnit())
|
||||
if (IsValidCandidate(target))
|
||||
return target
|
||||
&& target->IsAlive()
|
||||
&& (target->IsInCombat()
|
||||
|| target->GetVictim()
|
||||
|| !target->getAttackers().empty());
|
||||
};
|
||||
|
||||
auto isDeclaredTank = [owner](Unit* unit) -> bool
|
||||
{
|
||||
if (!unit)
|
||||
return false;
|
||||
|
||||
if (Creature* creature = unit->ToCreature())
|
||||
{
|
||||
if (CreatureTemplate const* creatureTemplate = creature->GetCreatureTemplate())
|
||||
{
|
||||
std::string const scriptName = sObjectMgr->GetScriptName(creatureTemplate->ScriptID);
|
||||
if (scriptName.find("_tank") != std::string::npos)
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
if (Player* player = unit->ToPlayer())
|
||||
{
|
||||
if (Group* group = owner->GetGroup())
|
||||
{
|
||||
if (group->isLFGGroup() && player->GetGroup() == group)
|
||||
{
|
||||
uint8 roles = sLFGMgr->GetRoles(player->GetGUID());
|
||||
if (roles & lfg::PLAYER_ROLE_TANK)
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
};
|
||||
|
||||
// VIANO_DD_TARGET_V1_1:
|
||||
// If a declared tank is actively fighting, DDs follow the tank's
|
||||
// victim. Merely selecting another hostile target must not pull it.
|
||||
std::list<Unit*> groupUnits;
|
||||
owner->GetPartyMembers(groupUnits);
|
||||
|
||||
Unit* bestTankTarget = nullptr;
|
||||
uint32 bestTankPressure = 0;
|
||||
bool declaredTankPresent = false;
|
||||
|
||||
auto considerTank = [&](Unit* tank)
|
||||
{
|
||||
if (!tank || !tank->IsAlive() || !tank->IsInMap(owner) || !isDeclaredTank(tank))
|
||||
return;
|
||||
|
||||
declaredTankPresent = true;
|
||||
|
||||
Unit* victim = tank->GetVictim();
|
||||
if (!victim || !IsValidCandidate(victim) || !isCombatRelevant(victim))
|
||||
return;
|
||||
|
||||
// VIANO_DD_TANK_ENGAGEMENT_GATE_V1:
|
||||
// AttackStart() gives the tank a victim immediately during a
|
||||
// skull pull. DDs must not interpret that as "tank has taken
|
||||
// the target" yet. Wait until the enemy is actually attacking
|
||||
// the tank / appears in the tank's attacker set.
|
||||
bool tankHasAggro = victim->GetVictim() == tank;
|
||||
if (!tankHasAggro)
|
||||
{
|
||||
for (Unit* attacker : tank->getAttackers())
|
||||
{
|
||||
if (attacker == victim)
|
||||
{
|
||||
tankHasAggro = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!tankHasAggro)
|
||||
return;
|
||||
|
||||
uint32 pressure = uint32(tank->getAttackers().size());
|
||||
if (!bestTankTarget || pressure > bestTankPressure)
|
||||
{
|
||||
bestTankTarget = victim;
|
||||
bestTankPressure = pressure;
|
||||
}
|
||||
};
|
||||
|
||||
considerTank(owner);
|
||||
for (Unit* member : groupUnits)
|
||||
considerTank(member);
|
||||
|
||||
if (bestTankTarget)
|
||||
return bestTankTarget;
|
||||
|
||||
// If a tank exists but has not actually established the pull yet,
|
||||
// DDs wait. In particular they must NOT fall through to the
|
||||
// player's selected skull target merely because the tank just put
|
||||
// that target into combat.
|
||||
if (declaredTankPresent)
|
||||
{
|
||||
if (Unit* target = me->GetVictim())
|
||||
if (IsValidCandidate(target) && isCombatRelevant(target))
|
||||
return target;
|
||||
|
||||
if (Unit* target = SelectGroupCombatTarget(owner))
|
||||
if (IsValidCandidate(target) && isCombatRelevant(target))
|
||||
return target;
|
||||
|
||||
for (Unit* attacker : me->getAttackers())
|
||||
if (IsValidCandidate(attacker) && isCombatRelevant(attacker))
|
||||
return attacker;
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
// 2. Keep the bot's own valid current victim.
|
||||
if (Unit* target = me->GetVictim())
|
||||
if (IsValidCandidate(target))
|
||||
// No declared tank: the owner controls DD focus, but only after
|
||||
// the target is already genuinely combat-relevant.
|
||||
if (Unit* target = owner->GetVictim())
|
||||
if (IsValidCandidate(target) && isCombatRelevant(target))
|
||||
return target;
|
||||
|
||||
// 3. Continue the group's fight even if the owner is a healer and
|
||||
// currently targets a friendly tank or another party member.
|
||||
if (Unit* target = SelectGroupCombatTarget(owner))
|
||||
return target;
|
||||
if (Unit* target = owner->GetSelectedUnit())
|
||||
if (IsValidCandidate(target) && isCombatRelevant(target))
|
||||
return target;
|
||||
|
||||
// Keep a valid existing bot victim.
|
||||
if (Unit* target = me->GetVictim())
|
||||
if (IsValidCandidate(target) && isCombatRelevant(target))
|
||||
return target;
|
||||
|
||||
// Help the virtual/real group against enemies that are already
|
||||
// attacking group members.
|
||||
if (Unit* target = SelectGroupCombatTarget(owner))
|
||||
if (IsValidCandidate(target) && isCombatRelevant(target))
|
||||
return target;
|
||||
|
||||
for (Unit* attacker : owner->getAttackers())
|
||||
if (IsValidCandidate(attacker) && isCombatRelevant(attacker))
|
||||
return attacker;
|
||||
|
||||
// 4. Finally, defend the bot itself if something is attacking it.
|
||||
for (Unit* attacker : me->getAttackers())
|
||||
if (IsValidCandidate(attacker))
|
||||
if (IsValidCandidate(attacker) && isCombatRelevant(attacker))
|
||||
return attacker;
|
||||
|
||||
return nullptr;
|
||||
@@ -570,7 +793,10 @@ namespace VianoAdventurers
|
||||
if (!owner)
|
||||
return;
|
||||
|
||||
me->GetMotionMaster()->MoveFollow(owner, FOLLOW_DISTANCE, FOLLOW_ANGLE);
|
||||
float followDistance = FOLLOW_DISTANCE;
|
||||
float followAngle = FOLLOW_ANGLE;
|
||||
GetVianoFollowSlot(me, owner, followDistance, followAngle);
|
||||
me->GetMotionMaster()->MoveFollow(owner, followDistance, followAngle);
|
||||
}
|
||||
|
||||
void HandleOutOfCombatRecovery(uint32 diff)
|
||||
@@ -617,13 +843,14 @@ namespace VianoAdventurers
|
||||
if (!owner)
|
||||
return nullptr;
|
||||
|
||||
Group* group = owner->GetGroup();
|
||||
if (!group)
|
||||
return nullptr;
|
||||
ObjectGuid guid;
|
||||
|
||||
ObjectGuid guid = group->GetTargetIcon(SKULL_ICON_ID);
|
||||
if (!guid)
|
||||
return nullptr;
|
||||
// A real Trinity Group is authoritative when it exists.
|
||||
if (Group* group = owner->GetGroup()) guid = group->GetTargetIcon(SKULL_ICON_ID);
|
||||
|
||||
if (!guid) guid = owner->GetVianoTargetIcon(SKULL_ICON_ID);
|
||||
|
||||
if (!guid) return nullptr;
|
||||
|
||||
Unit* target = ObjectAccessor::GetUnit(*me, guid);
|
||||
return IsValidTankTarget(target) ? target : nullptr;
|
||||
@@ -690,15 +917,72 @@ namespace VianoAdventurers
|
||||
&& me->IsWithinLOSInMap(enemy);
|
||||
}
|
||||
|
||||
// VIANO_MULTI_TANK_ARBITRATION_V1
|
||||
bool IsPreferredTankFor(Player* owner, Unit* enemy) const
|
||||
{
|
||||
if (!owner || !enemy)
|
||||
return false;
|
||||
|
||||
// Never steal an enemy that is already actively tanked by another
|
||||
// living Viano tank. Normal damage threat still works; only the
|
||||
// artificial recovery threat is suppressed.
|
||||
if (Unit* victim = enemy->GetVictim())
|
||||
{
|
||||
if (victim == me)
|
||||
return false;
|
||||
|
||||
if (victim->IsAlive() && IsVianoTankUnit(victim))
|
||||
return false;
|
||||
}
|
||||
|
||||
std::list<Unit*> groupUnits;
|
||||
owner->GetPartyMembers(groupUnits);
|
||||
|
||||
Creature* preferred = nullptr;
|
||||
float preferredDistance = 0.0f;
|
||||
|
||||
for (Unit* unit : groupUnits)
|
||||
{
|
||||
Creature* candidate = unit ? unit->ToCreature() : nullptr;
|
||||
if (!candidate || !candidate->IsAlive() || !candidate->IsInMap(owner))
|
||||
continue;
|
||||
if (!IsVianoTankUnit(candidate))
|
||||
continue;
|
||||
if (!candidate->IsValidAttackTarget(enemy))
|
||||
continue;
|
||||
if (candidate->GetDistance(enemy) > AUTO_PICKUP_RADIUS)
|
||||
continue;
|
||||
if (!candidate->IsWithinLOSInMap(enemy))
|
||||
continue;
|
||||
|
||||
float candidateDistance = candidate->GetDistance(enemy);
|
||||
if (!preferred
|
||||
|| candidateDistance < preferredDistance
|
||||
|| (candidateDistance == preferredDistance
|
||||
&& candidate->GetGUID().GetRawValue() < preferred->GetGUID().GetRawValue()))
|
||||
{
|
||||
preferred = candidate;
|
||||
preferredDistance = candidateDistance;
|
||||
}
|
||||
}
|
||||
|
||||
return preferred == me;
|
||||
}
|
||||
|
||||
void ForceTankThreat(Unit* enemy)
|
||||
{
|
||||
if (!IsSafeAutoPickup(enemy))
|
||||
return;
|
||||
|
||||
Player* owner = GetOwnerPlayer();
|
||||
if (!owner || !IsPreferredTankFor(owner, enemy))
|
||||
return;
|
||||
|
||||
ThreatManager& threat = enemy->GetThreatManager();
|
||||
|
||||
// Match the current highest threat, then exceed it by 35%.
|
||||
// This gives us taunt-like recovery without permanent Fixate.
|
||||
// Recovery only: reach the current highest threat and exceed it.
|
||||
// Once this tank actually owns the victim, IsPreferredTankFor()
|
||||
// stops further artificial inflation until aggro is lost again.
|
||||
threat.MatchUnitThreatToHighestThreat(me);
|
||||
|
||||
float current = threat.GetThreat(me, true);
|
||||
@@ -821,6 +1105,7 @@ namespace VianoAdventurers
|
||||
};
|
||||
|
||||
me->SetDisplayId(HORDE_ADVENTURER_MODELS[urand(0, 3)], true);
|
||||
SetEquipmentSlots(false, 50179, 50794, EQUIP_NO_CHANGE);
|
||||
me->SetReactState(REACT_DEFENSIVE);
|
||||
|
||||
FollowOwner(owner);
|
||||
@@ -949,6 +1234,446 @@ namespace VianoAdventurers
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
// VIANO_HEALER_V1
|
||||
class npc_horde_adventurer_healer : public CreatureScript
|
||||
{
|
||||
public:
|
||||
npc_horde_adventurer_healer() : CreatureScript("npc_horde_adventurer_healer") { }
|
||||
|
||||
struct npc_horde_adventurer_healerAI : public ScriptedAI
|
||||
{
|
||||
npc_horde_adventurer_healerAI(Creature* creature) : ScriptedAI(creature) { }
|
||||
|
||||
static constexpr uint32 SPELL_HEAL_EFFICIENT = 635;
|
||||
static constexpr uint32 SPELL_HEAL_STRONG = 82326;
|
||||
static constexpr uint32 SPELL_HEAL_EMERGENCY = 19750;
|
||||
static constexpr float HEAL_RANGE = 40.0f;
|
||||
|
||||
ObjectGuid ownerGuid;
|
||||
uint32 updateTimer = 0;
|
||||
uint32 recoveryTimer = 1000;
|
||||
uint32 decisionLockTimer = 0;
|
||||
|
||||
Player* GetOwnerPlayer() const
|
||||
{
|
||||
return ownerGuid ? ObjectAccessor::GetPlayer(*me, ownerGuid) : nullptr;
|
||||
}
|
||||
|
||||
static std::string GetScriptName(Creature const* creature)
|
||||
{
|
||||
if (!creature)
|
||||
return {};
|
||||
if (CreatureTemplate const* ct = creature->GetCreatureTemplate())
|
||||
return sObjectMgr->GetScriptName(ct->ScriptID);
|
||||
return {};
|
||||
}
|
||||
|
||||
bool IsVianoTank(Unit* unit) const
|
||||
{
|
||||
Creature* creature = unit ? unit->ToCreature() : nullptr;
|
||||
return creature && GetScriptName(creature).find("_tank") != std::string::npos;
|
||||
}
|
||||
|
||||
bool IsDeclaredPlayerTank(Unit* unit, Player* owner) const
|
||||
{
|
||||
Player* player = unit ? unit->ToPlayer() : nullptr;
|
||||
if (!player || !owner)
|
||||
return false;
|
||||
|
||||
Group* group = owner->GetGroup();
|
||||
if (!group || !group->isLFGGroup() || player->GetGroup() != group)
|
||||
return false;
|
||||
|
||||
uint8 roles = sLFGMgr->GetRoles(player->GetGUID());
|
||||
return (roles & lfg::PLAYER_ROLE_TANK) != 0;
|
||||
}
|
||||
|
||||
bool IsDeclaredTank(Unit* unit, Player* owner) const
|
||||
{
|
||||
return IsVianoTank(unit) || IsDeclaredPlayerTank(unit, owner);
|
||||
}
|
||||
|
||||
bool IsActiveTank(Unit* unit) const
|
||||
{
|
||||
return unit && unit->IsAlive() && !unit->getAttackers().empty();
|
||||
}
|
||||
|
||||
uint32 CountRelevantAttackers(Unit* unit) const
|
||||
{
|
||||
if (!unit)
|
||||
return 0;
|
||||
|
||||
uint32 count = 0;
|
||||
for (Unit* attacker : unit->getAttackers())
|
||||
if (attacker && attacker->IsAlive() && attacker->GetVictim() == unit)
|
||||
++count;
|
||||
return count;
|
||||
}
|
||||
|
||||
bool IsGroupCombatActive(Player* owner) const
|
||||
{
|
||||
if (!owner)
|
||||
return false;
|
||||
|
||||
std::list<Unit*> units;
|
||||
owner->GetPartyMembers(units);
|
||||
for (Unit* unit : units)
|
||||
if (unit && unit->IsAlive() && (!unit->getAttackers().empty() || unit->IsInCombat()))
|
||||
return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
bool IsValidHealTarget(Unit* unit, Player* owner) const
|
||||
{
|
||||
return unit
|
||||
&& owner
|
||||
&& unit->IsAlive()
|
||||
&& unit->IsInMap(owner)
|
||||
&& me->GetDistance(unit) <= HEAL_RANGE
|
||||
&& me->IsWithinLOSInMap(unit)
|
||||
&& me->IsValidAssistTarget(unit);
|
||||
}
|
||||
|
||||
float HealthPct(Unit* unit) const
|
||||
{
|
||||
return unit && unit->GetMaxHealth()
|
||||
? 100.0f * float(unit->GetHealth()) / float(unit->GetMaxHealth())
|
||||
: 100.0f;
|
||||
}
|
||||
|
||||
int32 HealPriority(Unit* unit, Player* owner) const
|
||||
{
|
||||
if (!IsValidHealTarget(unit, owner))
|
||||
return -1;
|
||||
|
||||
float hp = HealthPct(unit);
|
||||
bool declaredTank = IsDeclaredTank(unit, owner);
|
||||
bool activeTank = IsActiveTank(unit);
|
||||
uint32 attackers = CountRelevantAttackers(unit);
|
||||
|
||||
if (!declaredTank && !activeTank && hp > 75.0f)
|
||||
return -1;
|
||||
if ((declaredTank || activeTank) && hp > 95.0f)
|
||||
return -1;
|
||||
|
||||
int32 score = int32(100.0f - hp);
|
||||
if (declaredTank)
|
||||
score += 45;
|
||||
if (activeTank)
|
||||
score += 65;
|
||||
score += int32(std::min<uint32>(attackers, 5u)) * 12;
|
||||
|
||||
if (hp <= 35.0f)
|
||||
score += 120;
|
||||
else if (hp <= 50.0f)
|
||||
score += 55;
|
||||
|
||||
return score;
|
||||
}
|
||||
|
||||
Unit* SelectHealTarget(Player* owner) const
|
||||
{
|
||||
std::list<Unit*> units;
|
||||
owner->GetPartyMembers(units);
|
||||
|
||||
Unit* best = nullptr;
|
||||
int32 bestScore = -1;
|
||||
for (Unit* unit : units)
|
||||
{
|
||||
int32 score = HealPriority(unit, owner);
|
||||
if (score > bestScore)
|
||||
{
|
||||
best = unit;
|
||||
bestScore = score;
|
||||
}
|
||||
}
|
||||
return best;
|
||||
}
|
||||
|
||||
uint32 SelectHealSpell(Unit* target, Player* owner) const
|
||||
{
|
||||
float hp = HealthPct(target);
|
||||
bool tank = IsDeclaredTank(target, owner) || IsActiveTank(target);
|
||||
|
||||
if (hp <= 35.0f || (tank && hp <= 45.0f))
|
||||
return SPELL_HEAL_EMERGENCY;
|
||||
if (hp <= 50.0f || (tank && hp <= 68.0f))
|
||||
return SPELL_HEAL_STRONG;
|
||||
return SPELL_HEAL_EFFICIENT;
|
||||
}
|
||||
|
||||
bool ManaPolicyAllows(Unit* target, Player* owner, uint32 spellId) const
|
||||
{
|
||||
uint32 maxMana = me->GetMaxPower(POWER_MANA);
|
||||
if (!maxMana)
|
||||
return true;
|
||||
|
||||
float manaPct = 100.0f * float(me->GetPower(POWER_MANA)) / float(maxMana);
|
||||
float hp = HealthPct(target);
|
||||
bool tank = IsDeclaredTank(target, owner) || IsActiveTank(target);
|
||||
|
||||
if (manaPct < 10.0f)
|
||||
return spellId == SPELL_HEAL_EMERGENCY && hp <= 30.0f;
|
||||
|
||||
if (manaPct < 25.0f)
|
||||
{
|
||||
if (spellId == SPELL_HEAL_EMERGENCY)
|
||||
return true;
|
||||
if (tank)
|
||||
return hp <= 70.0f;
|
||||
return hp <= 45.0f;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void ConfigureMana(bool refill)
|
||||
{
|
||||
// VIANO_HEALER_BALANCE_V1_2
|
||||
// Player-like mana pool: level 31 -> 1802 mana.
|
||||
uint32 desired = 500u + uint32(me->getLevel()) * 42u;
|
||||
if (desired < 900u)
|
||||
desired = 900u;
|
||||
|
||||
uint32 oldMax = me->GetMaxPower(POWER_MANA);
|
||||
uint32 oldMana = me->GetPower(POWER_MANA);
|
||||
me->SetMaxPower(POWER_MANA, desired);
|
||||
|
||||
if (refill || !oldMax)
|
||||
me->SetPower(POWER_MANA, desired);
|
||||
else
|
||||
me->SetPower(POWER_MANA, uint32(std::min<uint64>(
|
||||
uint64(oldMana) * uint64(desired) / uint64(oldMax), uint64(desired))));
|
||||
|
||||
// Disable generic creature regen; Viano owns healer mana regen.
|
||||
me->RemoveFlag(UNIT_FIELD_FLAGS_2, UNIT_FLAG2_REGENERATE_POWER);
|
||||
}
|
||||
|
||||
void FollowOwner(Player* owner)
|
||||
{
|
||||
if (!owner)
|
||||
return;
|
||||
|
||||
uint32 slot = me->GetGUID().GetCounter() % 6u;
|
||||
static float const distances[6] = { 3.0f, 3.2f, 3.2f, 4.0f, 4.0f, 4.5f };
|
||||
static float const angles[6] = { 3.14159265f, 2.55f, 3.73f, 2.80f, 3.48f, 3.14159265f };
|
||||
me->GetMotionMaster()->MoveFollow(owner, distances[slot], angles[slot]);
|
||||
}
|
||||
|
||||
void HandleOutOfCombatRecovery(uint32 diff, Player* owner)
|
||||
{
|
||||
if (!me->IsAlive() || !owner)
|
||||
{
|
||||
recoveryTimer = 1000;
|
||||
return;
|
||||
}
|
||||
|
||||
if (recoveryTimer > diff)
|
||||
{
|
||||
recoveryTimer -= diff;
|
||||
return;
|
||||
}
|
||||
|
||||
recoveryTimer = 1000;
|
||||
bool const groupCombat = IsGroupCombatActive(owner);
|
||||
|
||||
if (!groupCombat)
|
||||
{
|
||||
uint32 maxHealth = me->GetMaxHealth();
|
||||
if (me->GetHealth() < maxHealth)
|
||||
{
|
||||
uint32 gain = std::max<uint32>(1u, maxHealth / 10u);
|
||||
me->SetHealth(std::min<uint32>(maxHealth, me->GetHealth() + gain));
|
||||
}
|
||||
}
|
||||
|
||||
uint32 maxMana = me->GetMaxPower(POWER_MANA);
|
||||
if (maxMana && me->GetPower(POWER_MANA) < maxMana)
|
||||
{
|
||||
uint32 divisor = groupCombat ? 100u : 30u; // 1% combat, ~3.3% OOC
|
||||
uint32 gain = std::max<uint32>(1u, maxMana / divisor);
|
||||
me->SetPower(POWER_MANA,
|
||||
std::min<uint32>(maxMana, me->GetPower(POWER_MANA) + gain));
|
||||
}
|
||||
}
|
||||
|
||||
void InitializeAI() override
|
||||
{
|
||||
ScriptedAI::InitializeAI();
|
||||
|
||||
TempSummon* summon = me->ToTempSummon();
|
||||
Player* owner = summon && summon->GetSummoner() ? summon->GetSummoner()->ToPlayer() : nullptr;
|
||||
if (!owner)
|
||||
{
|
||||
me->DespawnOrUnsummon();
|
||||
return;
|
||||
}
|
||||
|
||||
ownerGuid = owner->GetGUID();
|
||||
me->SetOwnerGUID(ownerGuid);
|
||||
me->SetFlag(UNIT_FIELD_FLAGS, UNIT_FLAG_PLAYER_CONTROLLED);
|
||||
me->SetFaction(owner->GetFaction());
|
||||
me->SetLevel(owner->getLevel());
|
||||
me->UpdateLevelDependantStats();
|
||||
ConfigureMana(true);
|
||||
|
||||
static constexpr uint32 MODELS[] = { 30063, 30064, 30065, 30066 };
|
||||
me->SetDisplayId(MODELS[urand(0, 3)], true);
|
||||
SetEquipmentSlots(false, 39426, EQUIP_NO_CHANGE, EQUIP_NO_CHANGE);
|
||||
me->SetReactState(REACT_PASSIVE);
|
||||
FollowOwner(owner);
|
||||
|
||||
TC_LOG_INFO("scripts",
|
||||
"[VianoHealer] initialized entry=%u owner='%s' level=%u hp=%u/%u mana=%u/%u",
|
||||
me->GetEntry(), owner->GetName().c_str(), uint32(me->getLevel()),
|
||||
me->GetHealth(), me->GetMaxHealth(),
|
||||
me->GetPower(POWER_MANA), me->GetMaxPower(POWER_MANA));
|
||||
}
|
||||
|
||||
void Reset() override
|
||||
{
|
||||
if (Player* owner = GetOwnerPlayer())
|
||||
FollowOwner(owner);
|
||||
}
|
||||
|
||||
void MoveInLineOfSight(Unit* /*who*/) override
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
// VIANO_HEALER_DISPEL_V1
|
||||
bool TryDispelGroupMember(Player* owner)
|
||||
{
|
||||
if (!owner || me->HasUnitState(UNIT_STATE_CASTING))
|
||||
return false;
|
||||
|
||||
std::list<Unit*> members;
|
||||
owner->GetPartyMembers(members);
|
||||
std::vector<Unit*> candidates;
|
||||
candidates.push_back(owner);
|
||||
|
||||
for (Unit* member : members)
|
||||
{
|
||||
if (!member)
|
||||
continue;
|
||||
bool duplicate = false;
|
||||
for (Unit* existing : candidates)
|
||||
if (existing && existing->GetGUID() == member->GetGUID())
|
||||
duplicate = true;
|
||||
if (!duplicate)
|
||||
candidates.push_back(member);
|
||||
}
|
||||
|
||||
// Healing wins if anyone is substantially hurt.
|
||||
for (Unit* unit : candidates)
|
||||
if (unit && unit->IsAlive() && unit->IsInMap(me) && unit->GetHealthPct() < 70.0f)
|
||||
return false;
|
||||
|
||||
uint32 const curseMask = (1u << DISPEL_CURSE);
|
||||
uint32 const cleanseMask = (1u << DISPEL_MAGIC) |
|
||||
(1u << DISPEL_DISEASE) | (1u << DISPEL_POISON);
|
||||
|
||||
for (Unit* unit : candidates)
|
||||
{
|
||||
if (!unit || !unit->IsAlive() || !unit->IsInMap(me))
|
||||
continue;
|
||||
if (!me->IsWithinDistInMap(unit, 40.0f) || !me->IsWithinLOSInMap(unit))
|
||||
continue;
|
||||
|
||||
DispelChargesList curseAuras;
|
||||
unit->GetDispellableAuraList(me, curseMask, curseAuras);
|
||||
if (!curseAuras.empty())
|
||||
{
|
||||
DoCast(unit, 2782); // Remove Corruption
|
||||
return true;
|
||||
}
|
||||
|
||||
DispelChargesList cleanseAuras;
|
||||
unit->GetDispellableAuraList(me, cleanseMask, cleanseAuras);
|
||||
if (!cleanseAuras.empty())
|
||||
{
|
||||
DoCast(unit, 4987); // Cleanse
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void UpdateAI(uint32 diff) override
|
||||
{
|
||||
if (updateTimer > diff)
|
||||
{
|
||||
updateTimer -= diff;
|
||||
decisionLockTimer = decisionLockTimer > diff ? decisionLockTimer - diff : 0;
|
||||
HandleOutOfCombatRecovery(diff, GetOwnerPlayer());
|
||||
return;
|
||||
}
|
||||
|
||||
updateTimer = 250;
|
||||
decisionLockTimer = decisionLockTimer > diff ? decisionLockTimer - diff : 0;
|
||||
|
||||
Player* owner = GetOwnerPlayer();
|
||||
|
||||
if (owner && TryDispelGroupMember(owner))
|
||||
return;
|
||||
if (!owner || !owner->IsInWorld() || !me->IsInMap(owner))
|
||||
{
|
||||
me->DespawnOrUnsummon();
|
||||
return;
|
||||
}
|
||||
|
||||
HandleOutOfCombatRecovery(diff, owner);
|
||||
|
||||
if (me->getLevel() != owner->getLevel())
|
||||
{
|
||||
me->SetLevel(owner->getLevel());
|
||||
me->UpdateLevelDependantStats();
|
||||
ConfigureMana(false);
|
||||
}
|
||||
|
||||
if (me->GetDistance(owner) > 45.0f && !me->HasUnitState(UNIT_STATE_CASTING))
|
||||
{
|
||||
me->GetMotionMaster()->Clear();
|
||||
me->NearTeleportTo(owner->GetPositionX() + 2.0f, owner->GetPositionY() + 1.0f,
|
||||
owner->GetPositionZ(), owner->GetOrientation());
|
||||
FollowOwner(owner);
|
||||
return;
|
||||
}
|
||||
|
||||
if (me->HasUnitState(UNIT_STATE_CASTING) || decisionLockTimer)
|
||||
return;
|
||||
|
||||
Unit* target = SelectHealTarget(owner);
|
||||
if (!target)
|
||||
{
|
||||
FollowOwner(owner);
|
||||
return;
|
||||
}
|
||||
|
||||
uint32 spellId = SelectHealSpell(target, owner);
|
||||
if (!ManaPolicyAllows(target, owner, spellId))
|
||||
return;
|
||||
|
||||
me->CastSpell(target, spellId);
|
||||
|
||||
TC_LOG_INFO("scripts",
|
||||
"[VianoHealer] cast spell=%u target='%s' hp=%.1f declaredTank=%u activeTank=%u attackers=%u mana=%u/%u",
|
||||
spellId, target->GetName().c_str(), HealthPct(target),
|
||||
IsDeclaredTank(target, owner) ? 1u : 0u,
|
||||
IsActiveTank(target) ? 1u : 0u,
|
||||
CountRelevantAttackers(target),
|
||||
me->GetPower(POWER_MANA), me->GetMaxPower(POWER_MANA));
|
||||
|
||||
decisionLockTimer = 500;
|
||||
}
|
||||
};
|
||||
|
||||
CreatureAI* GetAI(Creature* creature) const override
|
||||
{
|
||||
return new npc_horde_adventurer_healerAI(creature);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
void AddSC_viano_adventurers()
|
||||
@@ -956,4 +1681,5 @@ void AddSC_viano_adventurers()
|
||||
using namespace VianoAdventurers;
|
||||
new npc_horde_adventurer_melee();
|
||||
new npc_horde_adventurer_tank();
|
||||
new npc_horde_adventurer_healer();
|
||||
}
|
||||
@@ -1,477 +0,0 @@
|
||||
/*
|
||||
* Viano custom dungeon adventurers
|
||||
* Initial melee companion implementation.
|
||||
*/
|
||||
|
||||
#include "ScriptMgr.h"
|
||||
#include "ScriptedCreature.h"
|
||||
#include "ObjectAccessor.h"
|
||||
#include "Player.h"
|
||||
#include "TemporarySummon.h"
|
||||
#include "MotionMaster.h"
|
||||
#include "Log.h"
|
||||
|
||||
namespace VianoAdventurers
|
||||
{
|
||||
static constexpr float FOLLOW_DISTANCE = 2.5f;
|
||||
static constexpr float FOLLOW_ANGLE = 3.14159265f;
|
||||
static constexpr float TELEPORT_DISTANCE = 45.0f;
|
||||
static constexpr uint32 UPDATE_INTERVAL = 500;
|
||||
static constexpr uint32 DIAGNOSTIC_INTERVAL = 2000;
|
||||
|
||||
// VIANO_DD_PROFILE_V1_5
|
||||
enum AdventurerSpells : uint32
|
||||
{
|
||||
SPELL_ADVENTURER_MORTAL_STRIKE = 65926,
|
||||
SPELL_ADVENTURER_OVERPOWER = 65924,
|
||||
SPELL_ADVENTURER_BLADESTORM = 65947
|
||||
};
|
||||
|
||||
enum AdventurerEvents : uint32
|
||||
{
|
||||
EVENT_ADVENTURER_MORTAL_STRIKE = 1,
|
||||
EVENT_ADVENTURER_OVERPOWER,
|
||||
EVENT_ADVENTURER_BLADESTORM
|
||||
};
|
||||
|
||||
// VIANO_AI_DIAGNOSTICS_V1
|
||||
|
||||
class npc_horde_adventurer_melee : public CreatureScript
|
||||
{
|
||||
public:
|
||||
npc_horde_adventurer_melee() : CreatureScript("npc_horde_adventurer_melee") { }
|
||||
|
||||
struct npc_horde_adventurer_meleeAI : public ScriptedAI
|
||||
{
|
||||
npc_horde_adventurer_meleeAI(Creature* creature) : ScriptedAI(creature) { }
|
||||
|
||||
ObjectGuid ownerGuid;
|
||||
EventMap combatEvents;
|
||||
uint32 updateTimer = 0;
|
||||
uint32 diagnosticTimer = 0;
|
||||
uint32 recoveryTimer = 1000;
|
||||
Player* GetOwnerPlayer() const
|
||||
{
|
||||
if (!ownerGuid)
|
||||
return nullptr;
|
||||
|
||||
return ObjectAccessor::GetPlayer(*me, ownerGuid);
|
||||
}
|
||||
|
||||
void FollowOwner(Player* owner)
|
||||
{
|
||||
if (!owner)
|
||||
return;
|
||||
|
||||
me->GetMotionMaster()->MoveFollow(owner, FOLLOW_DISTANCE, FOLLOW_ANGLE);
|
||||
}
|
||||
|
||||
void ResetCombatEvents()
|
||||
{
|
||||
combatEvents.Reset();
|
||||
|
||||
combatEvents.ScheduleEvent(EVENT_ADVENTURER_MORTAL_STRIKE, 2 * IN_MILLISECONDS);
|
||||
combatEvents.ScheduleEvent(EVENT_ADVENTURER_OVERPOWER, 5 * IN_MILLISECONDS);
|
||||
combatEvents.ScheduleEvent(EVENT_ADVENTURER_BLADESTORM, 12 * IN_MILLISECONDS);
|
||||
}
|
||||
|
||||
void HandleCombatActions(uint32 diff)
|
||||
{
|
||||
Unit* victim = me->GetVictim();
|
||||
if (!victim || !victim->IsAlive())
|
||||
return;
|
||||
|
||||
combatEvents.Update(diff);
|
||||
|
||||
if (me->HasUnitState(UNIT_STATE_CASTING))
|
||||
return;
|
||||
|
||||
if (uint32 eventId = combatEvents.ExecuteEvent())
|
||||
{
|
||||
switch (eventId)
|
||||
{
|
||||
case EVENT_ADVENTURER_MORTAL_STRIKE:
|
||||
TC_LOG_INFO("scripts", "[VianoAI] cast Mortal Strike: target='%s'(%u)",
|
||||
UnitName(victim), UnitEntry(victim));
|
||||
DoCastVictim(SPELL_ADVENTURER_MORTAL_STRIKE);
|
||||
combatEvents.ScheduleEvent(EVENT_ADVENTURER_MORTAL_STRIKE,
|
||||
urand(6 * IN_MILLISECONDS, 8 * IN_MILLISECONDS));
|
||||
return;
|
||||
|
||||
case EVENT_ADVENTURER_OVERPOWER:
|
||||
TC_LOG_INFO("scripts", "[VianoAI] cast Overpower: target='%s'(%u)",
|
||||
UnitName(victim), UnitEntry(victim));
|
||||
DoCastVictim(SPELL_ADVENTURER_OVERPOWER);
|
||||
combatEvents.ScheduleEvent(EVENT_ADVENTURER_OVERPOWER,
|
||||
urand(10 * IN_MILLISECONDS, 14 * IN_MILLISECONDS));
|
||||
return;
|
||||
|
||||
case EVENT_ADVENTURER_BLADESTORM:
|
||||
TC_LOG_INFO("scripts", "[VianoAI] cast Bladestorm: target='%s'(%u)",
|
||||
UnitName(victim), UnitEntry(victim));
|
||||
DoCastVictim(SPELL_ADVENTURER_BLADESTORM);
|
||||
combatEvents.ScheduleEvent(EVENT_ADVENTURER_BLADESTORM,
|
||||
urand(30 * IN_MILLISECONDS, 45 * IN_MILLISECONDS));
|
||||
return;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
DoMeleeAttackIfReady();
|
||||
}
|
||||
void HandleOutOfCombatRecovery(uint32 diff)
|
||||
{
|
||||
if (!me->IsAlive())
|
||||
{
|
||||
recoveryTimer = 1000;
|
||||
return;
|
||||
}
|
||||
|
||||
// Keine künstliche Regeneration während eines Kampfes.
|
||||
if (me->IsInCombat())
|
||||
{
|
||||
recoveryTimer = 1000;
|
||||
return;
|
||||
}
|
||||
|
||||
if (recoveryTimer > diff)
|
||||
{
|
||||
recoveryTimer -= diff;
|
||||
return;
|
||||
}
|
||||
|
||||
recoveryTimer = 1000;
|
||||
|
||||
// 10 % der maximalen Lebenspunkte pro Sekunde.
|
||||
uint32 maxHealth = me->GetMaxHealth();
|
||||
|
||||
if (me->GetHealth() < maxHealth)
|
||||
{
|
||||
uint32 healthGain = maxHealth / 10;
|
||||
|
||||
if (healthGain == 0)
|
||||
healthGain = 1;
|
||||
|
||||
uint32 newHealth = me->GetHealth() + healthGain;
|
||||
|
||||
if (newHealth > maxHealth)
|
||||
newHealth = maxHealth;
|
||||
|
||||
me->SetHealth(newHealth);
|
||||
}
|
||||
|
||||
// 10 % des maximalen Manas pro Sekunde.
|
||||
uint32 maxMana = me->GetMaxPower(POWER_MANA);
|
||||
|
||||
if (maxMana > 0 && me->GetPower(POWER_MANA) < maxMana)
|
||||
{
|
||||
uint32 manaGain = maxMana / 10;
|
||||
|
||||
if (manaGain == 0)
|
||||
manaGain = 1;
|
||||
|
||||
uint32 newMana = me->GetPower(POWER_MANA) + manaGain;
|
||||
|
||||
if (newMana > maxMana)
|
||||
newMana = maxMana;
|
||||
|
||||
me->SetPower(POWER_MANA, newMana);
|
||||
}
|
||||
}
|
||||
|
||||
void InitializeAI() override
|
||||
{
|
||||
ScriptedAI::InitializeAI();
|
||||
|
||||
TempSummon* summon = me->ToTempSummon();
|
||||
if (!summon)
|
||||
{
|
||||
me->DespawnOrUnsummon();
|
||||
return;
|
||||
}
|
||||
|
||||
Unit* summoner = summon->GetSummoner();
|
||||
Player* owner = summoner ? summoner->ToPlayer() : nullptr;
|
||||
if (!owner)
|
||||
{
|
||||
me->DespawnOrUnsummon();
|
||||
return;
|
||||
}
|
||||
|
||||
ownerGuid = owner->GetGUID();
|
||||
|
||||
me->SetOwnerGUID(ownerGuid);
|
||||
me->SetFaction(owner->GetFaction());
|
||||
me->SetLevel(owner->getLevel());
|
||||
me->UpdateLevelDependantStats();
|
||||
|
||||
|
||||
static constexpr uint32 HORDE_ADVENTURER_MODELS[] =
|
||||
{
|
||||
30063,
|
||||
30064,
|
||||
30065,
|
||||
30066
|
||||
};
|
||||
uint32 displayId = HORDE_ADVENTURER_MODELS[urand(0, 3)];
|
||||
|
||||
me->SetDisplayId(displayId, true);
|
||||
|
||||
TC_LOG_INFO("scripts","[VianoAI] Horde adventurer display selected: %u", displayId);
|
||||
|
||||
// REACT_PASSIVE lets AttackStart() briefly set a victim, but the
|
||||
// core subsequently drops that victim again. We already suppress
|
||||
// autonomous proximity aggro in MoveInLineOfSight(), so DEFENSIVE
|
||||
// keeps an explicitly assigned assist target without making the
|
||||
// adventurer independently pull nearby creatures.
|
||||
me->SetReactState(REACT_DEFENSIVE);
|
||||
|
||||
TC_LOG_INFO("scripts",
|
||||
"[VianoAI] initialized me='%s' entry=%u owner='%s' ownerGuid=%s meFaction=%u ownerFaction=%u level=%u",
|
||||
me->GetName().c_str(), me->GetEntry(), owner->GetName().c_str(), ownerGuid.ToString().c_str(),
|
||||
me->GetFaction(), owner->GetFaction(), uint32(me->getLevel()));
|
||||
|
||||
ResetCombatEvents();
|
||||
FollowOwner(owner);
|
||||
}
|
||||
|
||||
void Reset() override
|
||||
{
|
||||
ResetCombatEvents();
|
||||
|
||||
if (Player* owner = GetOwnerPlayer())
|
||||
FollowOwner(owner);
|
||||
}
|
||||
|
||||
void MoveInLineOfSight(Unit* /*who*/) override
|
||||
{
|
||||
}
|
||||
|
||||
char const* UnitName(Unit* unit) const
|
||||
{
|
||||
return unit ? unit->GetName().c_str() : "<none>";
|
||||
}
|
||||
|
||||
uint32 UnitEntry(Unit* unit) const
|
||||
{
|
||||
return unit ? unit->GetEntry() : 0;
|
||||
}
|
||||
|
||||
bool IsValidCandidate(Unit* unit) const
|
||||
{
|
||||
return unit && unit->IsAlive() && me->IsValidAttackTarget(unit);
|
||||
}
|
||||
|
||||
void LogDiagnostics(Player* owner)
|
||||
{
|
||||
if (!owner)
|
||||
return;
|
||||
|
||||
Unit* ownerVictim = owner->GetVictim();
|
||||
Unit* selected = owner->GetSelectedUnit();
|
||||
Unit* meVictim = me->GetVictim();
|
||||
|
||||
Unit* firstOwnerAttacker = nullptr;
|
||||
for (Unit* attacker : owner->getAttackers())
|
||||
{
|
||||
if (attacker)
|
||||
{
|
||||
firstOwnerAttacker = attacker;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
Unit* firstMeAttacker = nullptr;
|
||||
for (Unit* attacker : me->getAttackers())
|
||||
{
|
||||
if (attacker)
|
||||
{
|
||||
firstMeAttacker = attacker;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
TC_LOG_INFO("scripts",
|
||||
"[VianoAI] me='%s' entry=%u lvl=%u faction=%u react=%u alive=%u combat=%u hp=%u/%u victim='%s'(%u) attackers=%u distance=%.2f",
|
||||
me->GetName().c_str(), me->GetEntry(), uint32(me->getLevel()), me->GetFaction(), uint32(me->GetReactState()),
|
||||
me->IsAlive() ? 1u : 0u, me->IsInCombat() ? 1u : 0u, me->GetHealth(), me->GetMaxHealth(),
|
||||
UnitName(meVictim), UnitEntry(meVictim), uint32(me->getAttackers().size()), me->GetDistance(owner));
|
||||
|
||||
TC_LOG_INFO("scripts",
|
||||
"[VianoAI] owner='%s' lvl=%u faction=%u alive=%u combat=%u victim='%s'(%u) selected='%s'(%u) attackers=%u",
|
||||
owner->GetName().c_str(), uint32(owner->getLevel()), owner->GetFaction(), owner->IsAlive() ? 1u : 0u,
|
||||
owner->IsInCombat() ? 1u : 0u, UnitName(ownerVictim), UnitEntry(ownerVictim), UnitName(selected), UnitEntry(selected),
|
||||
uint32(owner->getAttackers().size()));
|
||||
|
||||
if (ownerVictim)
|
||||
TC_LOG_INFO("scripts", "[VianoAI] ownerVictim='%s' entry=%u faction=%u validForBot=%u botCanAttack=%u ownerCanAttack=%u victim='%s'(%u)",
|
||||
ownerVictim->GetName().c_str(), ownerVictim->GetEntry(), ownerVictim->GetFaction(), IsValidCandidate(ownerVictim) ? 1u : 0u,
|
||||
me->IsValidAttackTarget(ownerVictim) ? 1u : 0u, owner->IsValidAttackTarget(ownerVictim) ? 1u : 0u,
|
||||
UnitName(ownerVictim->GetVictim()), UnitEntry(ownerVictim->GetVictim()));
|
||||
|
||||
if (selected)
|
||||
TC_LOG_INFO("scripts", "[VianoAI] selected='%s' entry=%u faction=%u validForBot=%u botCanAttack=%u ownerCanAttack=%u victim='%s'(%u)",
|
||||
selected->GetName().c_str(), selected->GetEntry(), selected->GetFaction(), IsValidCandidate(selected) ? 1u : 0u,
|
||||
me->IsValidAttackTarget(selected) ? 1u : 0u, owner->IsValidAttackTarget(selected) ? 1u : 0u,
|
||||
UnitName(selected->GetVictim()), UnitEntry(selected->GetVictim()));
|
||||
|
||||
if (firstOwnerAttacker)
|
||||
TC_LOG_INFO("scripts", "[VianoAI] firstOwnerAttacker='%s' entry=%u faction=%u validForBot=%u botCanAttack=%u ownerCanAttack=%u victim='%s'(%u)",
|
||||
firstOwnerAttacker->GetName().c_str(), firstOwnerAttacker->GetEntry(), firstOwnerAttacker->GetFaction(), IsValidCandidate(firstOwnerAttacker) ? 1u : 0u,
|
||||
me->IsValidAttackTarget(firstOwnerAttacker) ? 1u : 0u, owner->IsValidAttackTarget(firstOwnerAttacker) ? 1u : 0u,
|
||||
UnitName(firstOwnerAttacker->GetVictim()), UnitEntry(firstOwnerAttacker->GetVictim()));
|
||||
|
||||
if (firstMeAttacker)
|
||||
TC_LOG_INFO("scripts", "[VianoAI] firstMeAttacker='%s' entry=%u faction=%u victim='%s'(%u)",
|
||||
firstMeAttacker->GetName().c_str(), firstMeAttacker->GetEntry(), firstMeAttacker->GetFaction(),
|
||||
UnitName(firstMeAttacker->GetVictim()), UnitEntry(firstMeAttacker->GetVictim()));
|
||||
}
|
||||
|
||||
Unit* SelectAssistTarget(Player* owner)
|
||||
{
|
||||
if (!owner)
|
||||
return nullptr;
|
||||
|
||||
// 1. Ein aktives Ziel des Besitzers ist eine ausdrückliche
|
||||
// Zielanweisung und darf das aktuelle Bot-Ziel überschreiben.
|
||||
if (Unit* target = owner->GetVictim())
|
||||
if (IsValidCandidate(target))
|
||||
return target;
|
||||
|
||||
// GetVictim() ist beim Spieler nicht immer zuverlässig gesetzt.
|
||||
// Während eines bereits laufenden Kampfes darf deshalb auch ein
|
||||
// bewusst ausgewähltes lebendes Feindziel als Anweisung gelten.
|
||||
// Reines Anwählen außerhalb des Kampfes darf weiterhin NICHT pullen.
|
||||
if (owner->IsInCombat())
|
||||
{
|
||||
if (Unit* target = owner->GetSelectedUnit())
|
||||
if (IsValidCandidate(target))
|
||||
return target;
|
||||
}
|
||||
|
||||
// 2. Sobald der Bot selbst einen gültigen Gegner bekämpft,
|
||||
// hält er an diesem Ziel fest. ESC, eine Leiche als Target oder
|
||||
// fehlendes Spielertarget beendet den Kampf dadurch nicht mehr.
|
||||
if (Unit* target = me->GetVictim())
|
||||
if (IsValidCandidate(target))
|
||||
return target;
|
||||
|
||||
// 3. Falls das bisherige Ziel tot/ungültig ist, helfen wir dem
|
||||
// Besitzer gegen einen Gegner, der ihn bereits angreift.
|
||||
for (Unit* attacker : owner->getAttackers())
|
||||
if (IsValidCandidate(attacker))
|
||||
return attacker;
|
||||
|
||||
// 4. Danach übernimmt der Bot einen Gegner, der ihn selbst angreift.
|
||||
// Besonders wichtig, wenn sein vorheriges Ziel gerade gestorben ist.
|
||||
for (Unit* attacker : me->getAttackers())
|
||||
if (IsValidCandidate(attacker))
|
||||
return attacker;
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
void UpdateAI(uint32 diff) override
|
||||
{
|
||||
HandleOutOfCombatRecovery(diff);
|
||||
if (diagnosticTimer > diff)
|
||||
diagnosticTimer -= diff;
|
||||
else
|
||||
diagnosticTimer = 0;
|
||||
|
||||
if (updateTimer > diff)
|
||||
{
|
||||
updateTimer -= diff;
|
||||
|
||||
// VIANO_TARGET_CONTROL_V1_4
|
||||
// Keep the explicitly assigned victim. UpdateVictim() may
|
||||
// select another creature from the threat list.
|
||||
if (me->GetVictim())
|
||||
HandleCombatActions(diff);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
updateTimer = UPDATE_INTERVAL;
|
||||
|
||||
Player* owner = GetOwnerPlayer();
|
||||
if (!owner || !owner->IsInWorld() || !me->IsInMap(owner))
|
||||
{
|
||||
TC_LOG_INFO("scripts", "[VianoAI] despawn: owner invalid or not on same map (ownerFound=%u)", owner ? 1u : 0u);
|
||||
me->DespawnOrUnsummon();
|
||||
return;
|
||||
}
|
||||
|
||||
if (diagnosticTimer == 0)
|
||||
{
|
||||
diagnosticTimer = DIAGNOSTIC_INTERVAL;
|
||||
LogDiagnostics(owner);
|
||||
}
|
||||
|
||||
if (me->getLevel() != owner->getLevel())
|
||||
{
|
||||
me->SetLevel(owner->getLevel());
|
||||
me->UpdateLevelDependantStats();
|
||||
}
|
||||
|
||||
if (me->GetDistance(owner) > TELEPORT_DISTANCE)
|
||||
{
|
||||
me->CombatStop(true);
|
||||
me->GetMotionMaster()->Clear();
|
||||
|
||||
me->NearTeleportTo(
|
||||
owner->GetPositionX() + 1.5f,
|
||||
owner->GetPositionY(),
|
||||
owner->GetPositionZ(),
|
||||
owner->GetOrientation()
|
||||
);
|
||||
|
||||
FollowOwner(owner);
|
||||
return;
|
||||
}
|
||||
|
||||
Unit* desiredTarget = SelectAssistTarget(owner);
|
||||
|
||||
if (desiredTarget)
|
||||
{
|
||||
if (me->GetVictim() != desiredTarget)
|
||||
{
|
||||
TC_LOG_INFO("scripts",
|
||||
"[VianoAI] AttackStart requested: target='%s' entry=%u targetFaction=%u valid=%u meCombatBefore=%u",
|
||||
desiredTarget->GetName().c_str(), desiredTarget->GetEntry(), desiredTarget->GetFaction(),
|
||||
me->IsValidAttackTarget(desiredTarget) ? 1u : 0u, me->IsInCombat() ? 1u : 0u);
|
||||
|
||||
AttackStart(desiredTarget);
|
||||
|
||||
Unit* afterVictim = me->GetVictim();
|
||||
TC_LOG_INFO("scripts", "[VianoAI] AttackStart result: meCombatAfter=%u victim='%s'(%u)",
|
||||
me->IsInCombat() ? 1u : 0u, UnitName(afterVictim), UnitEntry(afterVictim));
|
||||
}
|
||||
}
|
||||
else if (!me->IsInCombat() && !me->GetVictim())
|
||||
{
|
||||
FollowOwner(owner);
|
||||
}
|
||||
|
||||
// VIANO_TARGET_CONTROL_V1_4
|
||||
// Keep the explicitly assigned victim. UpdateVictim() may
|
||||
// select another creature from the threat list.
|
||||
if (me->GetVictim())
|
||||
HandleCombatActions(diff);
|
||||
}
|
||||
};
|
||||
|
||||
CreatureAI* GetAI(Creature* creature) const override
|
||||
{
|
||||
return new npc_horde_adventurer_meleeAI(creature);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
void AddSC_viano_adventurers()
|
||||
{
|
||||
using namespace VianoAdventurers;
|
||||
new npc_horde_adventurer_melee();
|
||||
}
|
||||
-516
@@ -1,516 +0,0 @@
|
||||
/*
|
||||
* Viano custom dungeon adventurers
|
||||
* Initial melee companion implementation.
|
||||
*/
|
||||
|
||||
#include "ScriptMgr.h"
|
||||
#include "ScriptedCreature.h"
|
||||
#include "ObjectAccessor.h"
|
||||
#include "Player.h"
|
||||
#include "TemporarySummon.h"
|
||||
#include "MotionMaster.h"
|
||||
#include "Log.h"
|
||||
|
||||
namespace VianoAdventurers
|
||||
{
|
||||
static constexpr float FOLLOW_DISTANCE = 2.5f;
|
||||
static constexpr float FOLLOW_ANGLE = 3.14159265f;
|
||||
static constexpr float TELEPORT_DISTANCE = 45.0f;
|
||||
static constexpr uint32 UPDATE_INTERVAL = 500;
|
||||
static constexpr uint32 DIAGNOSTIC_INTERVAL = 2000;
|
||||
|
||||
// VIANO_DD_PROFILE_V1_5
|
||||
enum AdventurerSpells : uint32
|
||||
{
|
||||
SPELL_ADVENTURER_MORTAL_STRIKE = 65926,
|
||||
SPELL_ADVENTURER_OVERPOWER = 65924,
|
||||
SPELL_ADVENTURER_BLADESTORM = 65947
|
||||
};
|
||||
|
||||
enum AdventurerEvents : uint32
|
||||
{
|
||||
EVENT_ADVENTURER_MORTAL_STRIKE = 1,
|
||||
EVENT_ADVENTURER_OVERPOWER,
|
||||
EVENT_ADVENTURER_BLADESTORM
|
||||
};
|
||||
|
||||
// VIANO_AI_DIAGNOSTICS_V1
|
||||
|
||||
class npc_horde_adventurer_melee : public CreatureScript
|
||||
{
|
||||
public:
|
||||
npc_horde_adventurer_melee() : CreatureScript("npc_horde_adventurer_melee") { }
|
||||
|
||||
struct npc_horde_adventurer_meleeAI : public ScriptedAI
|
||||
{
|
||||
npc_horde_adventurer_meleeAI(Creature* creature) : ScriptedAI(creature) { }
|
||||
|
||||
ObjectGuid ownerGuid;
|
||||
EventMap combatEvents;
|
||||
uint32 updateTimer = 0;
|
||||
uint32 diagnosticTimer = 0;
|
||||
uint32 recoveryTimer = 1000;
|
||||
Player* GetOwnerPlayer() const
|
||||
{
|
||||
if (!ownerGuid)
|
||||
return nullptr;
|
||||
|
||||
return ObjectAccessor::GetPlayer(*me, ownerGuid);
|
||||
}
|
||||
|
||||
void FollowOwner(Player* owner)
|
||||
{
|
||||
if (!owner)
|
||||
return;
|
||||
|
||||
me->GetMotionMaster()->MoveFollow(owner, FOLLOW_DISTANCE, FOLLOW_ANGLE);
|
||||
}
|
||||
|
||||
void ResetCombatEvents()
|
||||
{
|
||||
combatEvents.Reset();
|
||||
|
||||
combatEvents.ScheduleEvent(EVENT_ADVENTURER_MORTAL_STRIKE, 2 * IN_MILLISECONDS);
|
||||
combatEvents.ScheduleEvent(EVENT_ADVENTURER_OVERPOWER, 5 * IN_MILLISECONDS);
|
||||
combatEvents.ScheduleEvent(EVENT_ADVENTURER_BLADESTORM, 12 * IN_MILLISECONDS);
|
||||
}
|
||||
|
||||
void HandleCombatActions(uint32 diff)
|
||||
{
|
||||
Unit* victim = me->GetVictim();
|
||||
if (!victim || !victim->IsAlive())
|
||||
return;
|
||||
|
||||
combatEvents.Update(diff);
|
||||
|
||||
if (me->HasUnitState(UNIT_STATE_CASTING))
|
||||
return;
|
||||
|
||||
if (uint32 eventId = combatEvents.ExecuteEvent())
|
||||
{
|
||||
switch (eventId)
|
||||
{
|
||||
case EVENT_ADVENTURER_MORTAL_STRIKE:
|
||||
TC_LOG_INFO("scripts", "[VianoAI] cast Mortal Strike: target='%s'(%u)",
|
||||
UnitName(victim), UnitEntry(victim));
|
||||
DoCastVictim(SPELL_ADVENTURER_MORTAL_STRIKE);
|
||||
combatEvents.ScheduleEvent(EVENT_ADVENTURER_MORTAL_STRIKE,
|
||||
urand(6 * IN_MILLISECONDS, 8 * IN_MILLISECONDS));
|
||||
return;
|
||||
|
||||
case EVENT_ADVENTURER_OVERPOWER:
|
||||
TC_LOG_INFO("scripts", "[VianoAI] cast Overpower: target='%s'(%u)",
|
||||
UnitName(victim), UnitEntry(victim));
|
||||
DoCastVictim(SPELL_ADVENTURER_OVERPOWER);
|
||||
combatEvents.ScheduleEvent(EVENT_ADVENTURER_OVERPOWER,
|
||||
urand(10 * IN_MILLISECONDS, 14 * IN_MILLISECONDS));
|
||||
return;
|
||||
|
||||
case EVENT_ADVENTURER_BLADESTORM:
|
||||
TC_LOG_INFO("scripts", "[VianoAI] cast Bladestorm: target='%s'(%u)",
|
||||
UnitName(victim), UnitEntry(victim));
|
||||
DoCastVictim(SPELL_ADVENTURER_BLADESTORM);
|
||||
combatEvents.ScheduleEvent(EVENT_ADVENTURER_BLADESTORM,
|
||||
urand(30 * IN_MILLISECONDS, 45 * IN_MILLISECONDS));
|
||||
return;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
DoMeleeAttackIfReady();
|
||||
}
|
||||
void HandleOutOfCombatRecovery(uint32 diff)
|
||||
{
|
||||
if (!me->IsAlive())
|
||||
{
|
||||
recoveryTimer = 1000;
|
||||
return;
|
||||
}
|
||||
|
||||
// Keine künstliche Regeneration während eines Kampfes.
|
||||
if (me->IsInCombat())
|
||||
{
|
||||
recoveryTimer = 1000;
|
||||
return;
|
||||
}
|
||||
|
||||
if (recoveryTimer > diff)
|
||||
{
|
||||
recoveryTimer -= diff;
|
||||
return;
|
||||
}
|
||||
|
||||
recoveryTimer = 1000;
|
||||
|
||||
// 10 % der maximalen Lebenspunkte pro Sekunde.
|
||||
uint32 maxHealth = me->GetMaxHealth();
|
||||
|
||||
if (me->GetHealth() < maxHealth)
|
||||
{
|
||||
uint32 healthGain = maxHealth / 10;
|
||||
|
||||
if (healthGain == 0)
|
||||
healthGain = 1;
|
||||
|
||||
uint32 newHealth = me->GetHealth() + healthGain;
|
||||
|
||||
if (newHealth > maxHealth)
|
||||
newHealth = maxHealth;
|
||||
|
||||
me->SetHealth(newHealth);
|
||||
}
|
||||
|
||||
// 10 % des maximalen Manas pro Sekunde.
|
||||
uint32 maxMana = me->GetMaxPower(POWER_MANA);
|
||||
|
||||
if (maxMana > 0 && me->GetPower(POWER_MANA) < maxMana)
|
||||
{
|
||||
uint32 manaGain = maxMana / 10;
|
||||
|
||||
if (manaGain == 0)
|
||||
manaGain = 1;
|
||||
|
||||
uint32 newMana = me->GetPower(POWER_MANA) + manaGain;
|
||||
|
||||
if (newMana > maxMana)
|
||||
newMana = maxMana;
|
||||
|
||||
me->SetPower(POWER_MANA, newMana);
|
||||
}
|
||||
}
|
||||
|
||||
void InitializeAI() override
|
||||
{
|
||||
ScriptedAI::InitializeAI();
|
||||
|
||||
TempSummon* summon = me->ToTempSummon();
|
||||
if (!summon)
|
||||
{
|
||||
me->DespawnOrUnsummon();
|
||||
return;
|
||||
}
|
||||
|
||||
Unit* summoner = summon->GetSummoner();
|
||||
Player* owner = summoner ? summoner->ToPlayer() : nullptr;
|
||||
if (!owner)
|
||||
{
|
||||
me->DespawnOrUnsummon();
|
||||
return;
|
||||
}
|
||||
|
||||
ownerGuid = owner->GetGUID();
|
||||
|
||||
me->SetOwnerGUID(ownerGuid);
|
||||
me->SetFaction(owner->GetFaction());
|
||||
me->SetLevel(owner->getLevel());
|
||||
me->UpdateLevelDependantStats();
|
||||
|
||||
|
||||
static constexpr uint32 HORDE_ADVENTURER_MODELS[] =
|
||||
{
|
||||
30063,
|
||||
30064,
|
||||
30065,
|
||||
30066
|
||||
};
|
||||
uint32 displayId = HORDE_ADVENTURER_MODELS[urand(0, 3)];
|
||||
|
||||
me->SetDisplayId(displayId, true);
|
||||
|
||||
TC_LOG_INFO("scripts","[VianoAI] Horde adventurer display selected: %u", displayId);
|
||||
|
||||
// REACT_PASSIVE lets AttackStart() briefly set a victim, but the
|
||||
// core subsequently drops that victim again. We already suppress
|
||||
// autonomous proximity aggro in MoveInLineOfSight(), so DEFENSIVE
|
||||
// keeps an explicitly assigned assist target without making the
|
||||
// adventurer independently pull nearby creatures.
|
||||
me->SetReactState(REACT_DEFENSIVE);
|
||||
|
||||
TC_LOG_INFO("scripts",
|
||||
"[VianoAI] initialized me='%s' entry=%u owner='%s' ownerGuid=%s meFaction=%u ownerFaction=%u level=%u",
|
||||
me->GetName().c_str(), me->GetEntry(), owner->GetName().c_str(), ownerGuid.ToString().c_str(),
|
||||
me->GetFaction(), owner->GetFaction(), uint32(me->getLevel()));
|
||||
|
||||
ResetCombatEvents();
|
||||
FollowOwner(owner);
|
||||
}
|
||||
|
||||
void Reset() override
|
||||
{
|
||||
ResetCombatEvents();
|
||||
|
||||
if (Player* owner = GetOwnerPlayer())
|
||||
FollowOwner(owner);
|
||||
}
|
||||
|
||||
void MoveInLineOfSight(Unit* /*who*/) override
|
||||
{
|
||||
}
|
||||
|
||||
char const* UnitName(Unit* unit) const
|
||||
{
|
||||
return unit ? unit->GetName().c_str() : "<none>";
|
||||
}
|
||||
|
||||
uint32 UnitEntry(Unit* unit) const
|
||||
{
|
||||
return unit ? unit->GetEntry() : 0;
|
||||
}
|
||||
|
||||
bool IsValidCandidate(Unit* unit) const
|
||||
{
|
||||
return unit && unit->IsAlive() && me->IsValidAttackTarget(unit);
|
||||
}
|
||||
|
||||
void LogDiagnostics(Player* owner)
|
||||
{
|
||||
if (!owner)
|
||||
return;
|
||||
|
||||
Unit* ownerVictim = owner->GetVictim();
|
||||
Unit* selected = owner->GetSelectedUnit();
|
||||
Unit* meVictim = me->GetVictim();
|
||||
|
||||
Unit* firstOwnerAttacker = nullptr;
|
||||
for (Unit* attacker : owner->getAttackers())
|
||||
{
|
||||
if (attacker)
|
||||
{
|
||||
firstOwnerAttacker = attacker;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
Unit* firstMeAttacker = nullptr;
|
||||
for (Unit* attacker : me->getAttackers())
|
||||
{
|
||||
if (attacker)
|
||||
{
|
||||
firstMeAttacker = attacker;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
TC_LOG_INFO("scripts",
|
||||
"[VianoAI] me='%s' entry=%u lvl=%u faction=%u react=%u alive=%u combat=%u hp=%u/%u victim='%s'(%u) attackers=%u distance=%.2f",
|
||||
me->GetName().c_str(), me->GetEntry(), uint32(me->getLevel()), me->GetFaction(), uint32(me->GetReactState()),
|
||||
me->IsAlive() ? 1u : 0u, me->IsInCombat() ? 1u : 0u, me->GetHealth(), me->GetMaxHealth(),
|
||||
UnitName(meVictim), UnitEntry(meVictim), uint32(me->getAttackers().size()), me->GetDistance(owner));
|
||||
|
||||
TC_LOG_INFO("scripts",
|
||||
"[VianoAI] owner='%s' lvl=%u faction=%u alive=%u combat=%u victim='%s'(%u) selected='%s'(%u) attackers=%u",
|
||||
owner->GetName().c_str(), uint32(owner->getLevel()), owner->GetFaction(), owner->IsAlive() ? 1u : 0u,
|
||||
owner->IsInCombat() ? 1u : 0u, UnitName(ownerVictim), UnitEntry(ownerVictim), UnitName(selected), UnitEntry(selected),
|
||||
uint32(owner->getAttackers().size()));
|
||||
|
||||
if (ownerVictim)
|
||||
TC_LOG_INFO("scripts", "[VianoAI] ownerVictim='%s' entry=%u faction=%u validForBot=%u botCanAttack=%u ownerCanAttack=%u victim='%s'(%u)",
|
||||
ownerVictim->GetName().c_str(), ownerVictim->GetEntry(), ownerVictim->GetFaction(), IsValidCandidate(ownerVictim) ? 1u : 0u,
|
||||
me->IsValidAttackTarget(ownerVictim) ? 1u : 0u, owner->IsValidAttackTarget(ownerVictim) ? 1u : 0u,
|
||||
UnitName(ownerVictim->GetVictim()), UnitEntry(ownerVictim->GetVictim()));
|
||||
|
||||
if (selected)
|
||||
TC_LOG_INFO("scripts", "[VianoAI] selected='%s' entry=%u faction=%u validForBot=%u botCanAttack=%u ownerCanAttack=%u victim='%s'(%u)",
|
||||
selected->GetName().c_str(), selected->GetEntry(), selected->GetFaction(), IsValidCandidate(selected) ? 1u : 0u,
|
||||
me->IsValidAttackTarget(selected) ? 1u : 0u, owner->IsValidAttackTarget(selected) ? 1u : 0u,
|
||||
UnitName(selected->GetVictim()), UnitEntry(selected->GetVictim()));
|
||||
|
||||
if (firstOwnerAttacker)
|
||||
TC_LOG_INFO("scripts", "[VianoAI] firstOwnerAttacker='%s' entry=%u faction=%u validForBot=%u botCanAttack=%u ownerCanAttack=%u victim='%s'(%u)",
|
||||
firstOwnerAttacker->GetName().c_str(), firstOwnerAttacker->GetEntry(), firstOwnerAttacker->GetFaction(), IsValidCandidate(firstOwnerAttacker) ? 1u : 0u,
|
||||
me->IsValidAttackTarget(firstOwnerAttacker) ? 1u : 0u, owner->IsValidAttackTarget(firstOwnerAttacker) ? 1u : 0u,
|
||||
UnitName(firstOwnerAttacker->GetVictim()), UnitEntry(firstOwnerAttacker->GetVictim()));
|
||||
|
||||
if (firstMeAttacker)
|
||||
TC_LOG_INFO("scripts", "[VianoAI] firstMeAttacker='%s' entry=%u faction=%u victim='%s'(%u)",
|
||||
firstMeAttacker->GetName().c_str(), firstMeAttacker->GetEntry(), firstMeAttacker->GetFaction(),
|
||||
UnitName(firstMeAttacker->GetVictim()), UnitEntry(firstMeAttacker->GetVictim()));
|
||||
}
|
||||
|
||||
bool IsActivelyEngagedInCombat() const
|
||||
{
|
||||
// IsInCombat() alone is intentionally not enough here: the Core
|
||||
// may keep the combat flag set after the bot has no live opponent.
|
||||
if (Unit* victim = me->GetVictim())
|
||||
if (IsValidCandidate(victim))
|
||||
return true;
|
||||
|
||||
for (Unit* attacker : me->getAttackers())
|
||||
if (IsValidCandidate(attacker))
|
||||
return true;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
Unit* SelectGroupCombatTarget(Player* owner)
|
||||
{
|
||||
if (!owner)
|
||||
return nullptr;
|
||||
|
||||
std::list<Unit*> partyMembers;
|
||||
owner->GetPartyMembers(partyMembers);
|
||||
|
||||
// In Vcore GetPartyMembers() returns living members of the same
|
||||
// party/subgroup on the same map, plus their living guardian pets.
|
||||
// An enemy attacking one of them is therefore already part of the
|
||||
// group's current fight.
|
||||
for (Unit* member : partyMembers)
|
||||
{
|
||||
if (!member || !member->IsAlive())
|
||||
continue;
|
||||
|
||||
for (Unit* attacker : member->getAttackers())
|
||||
if (IsValidCandidate(attacker))
|
||||
return attacker;
|
||||
}
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
Unit* SelectAssistTarget(Player* owner)
|
||||
{
|
||||
if (!owner)
|
||||
return nullptr;
|
||||
|
||||
// 1. An active owner victim is an explicit target instruction.
|
||||
if (Unit* target = owner->GetVictim())
|
||||
if (IsValidCandidate(target))
|
||||
return target;
|
||||
|
||||
// During an already running fight, an explicitly selected living
|
||||
// hostile target may also be treated as an instruction. Merely
|
||||
// selecting an enemy out of combat still must not pull it.
|
||||
if (owner->IsInCombat())
|
||||
{
|
||||
if (Unit* target = owner->GetSelectedUnit())
|
||||
if (IsValidCandidate(target))
|
||||
return target;
|
||||
}
|
||||
|
||||
// 2. Keep the bot's own valid current victim.
|
||||
if (Unit* target = me->GetVictim())
|
||||
if (IsValidCandidate(target))
|
||||
return target;
|
||||
|
||||
// 3. Continue the group's fight even if the owner is a healer and
|
||||
// currently targets a friendly tank or another party member.
|
||||
if (Unit* target = SelectGroupCombatTarget(owner))
|
||||
return target;
|
||||
|
||||
// 4. Finally, defend the bot itself if something is attacking it.
|
||||
for (Unit* attacker : me->getAttackers())
|
||||
if (IsValidCandidate(attacker))
|
||||
return attacker;
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
void UpdateAI(uint32 diff) override
|
||||
{
|
||||
HandleOutOfCombatRecovery(diff);
|
||||
if (diagnosticTimer > diff)
|
||||
diagnosticTimer -= diff;
|
||||
else
|
||||
diagnosticTimer = 0;
|
||||
|
||||
if (updateTimer > diff)
|
||||
{
|
||||
updateTimer -= diff;
|
||||
|
||||
// VIANO_TARGET_CONTROL_V1_4
|
||||
// Keep the explicitly assigned victim. UpdateVictim() may
|
||||
// select another creature from the threat list.
|
||||
if (me->GetVictim())
|
||||
HandleCombatActions(diff);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
updateTimer = UPDATE_INTERVAL;
|
||||
|
||||
Player* owner = GetOwnerPlayer();
|
||||
if (!owner || !owner->IsInWorld() || !me->IsInMap(owner))
|
||||
{
|
||||
TC_LOG_INFO("scripts", "[VianoAI] despawn: owner invalid or not on same map (ownerFound=%u)", owner ? 1u : 0u);
|
||||
me->DespawnOrUnsummon();
|
||||
return;
|
||||
}
|
||||
|
||||
if (diagnosticTimer == 0)
|
||||
{
|
||||
diagnosticTimer = DIAGNOSTIC_INTERVAL;
|
||||
LogDiagnostics(owner);
|
||||
}
|
||||
|
||||
if (me->getLevel() != owner->getLevel())
|
||||
{
|
||||
me->SetLevel(owner->getLevel());
|
||||
me->UpdateLevelDependantStats();
|
||||
}
|
||||
|
||||
if (me->GetDistance(owner) > TELEPORT_DISTANCE && !IsActivelyEngagedInCombat())
|
||||
{
|
||||
// Keep the recovery teleport for geometry/jump problems, even
|
||||
// if the group is already fighting elsewhere. Only the bot's
|
||||
// own active engagement blocks teleporting.
|
||||
me->GetMotionMaster()->Clear();
|
||||
|
||||
me->NearTeleportTo(
|
||||
owner->GetPositionX() + 1.5f,
|
||||
owner->GetPositionY(),
|
||||
owner->GetPositionZ(),
|
||||
owner->GetOrientation()
|
||||
);
|
||||
|
||||
FollowOwner(owner);
|
||||
return;
|
||||
}
|
||||
|
||||
Unit* desiredTarget = SelectAssistTarget(owner);
|
||||
|
||||
if (desiredTarget)
|
||||
{
|
||||
if (me->GetVictim() != desiredTarget)
|
||||
{
|
||||
TC_LOG_INFO("scripts",
|
||||
"[VianoAI] AttackStart requested: target='%s' entry=%u targetFaction=%u valid=%u meCombatBefore=%u",
|
||||
desiredTarget->GetName().c_str(), desiredTarget->GetEntry(), desiredTarget->GetFaction(),
|
||||
me->IsValidAttackTarget(desiredTarget) ? 1u : 0u, me->IsInCombat() ? 1u : 0u);
|
||||
|
||||
AttackStart(desiredTarget);
|
||||
|
||||
Unit* afterVictim = me->GetVictim();
|
||||
TC_LOG_INFO("scripts", "[VianoAI] AttackStart result: meCombatAfter=%u victim='%s'(%u)",
|
||||
me->IsInCombat() ? 1u : 0u, UnitName(afterVictim), UnitEntry(afterVictim));
|
||||
}
|
||||
}
|
||||
else if (!IsActivelyEngagedInCombat())
|
||||
{
|
||||
// A stale Core combat flag must not prevent recovery/following.
|
||||
// We deliberately do not clear the flag here; OOC regeneration
|
||||
// will be handled as a separate change.
|
||||
FollowOwner(owner);
|
||||
}
|
||||
|
||||
// VIANO_TARGET_CONTROL_V1_4
|
||||
// Keep the explicitly assigned victim. UpdateVictim() may
|
||||
// select another creature from the threat list.
|
||||
if (me->GetVictim())
|
||||
HandleCombatActions(diff);
|
||||
}
|
||||
};
|
||||
|
||||
CreatureAI* GetAI(Creature* creature) const override
|
||||
{
|
||||
return new npc_horde_adventurer_meleeAI(creature);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
void AddSC_viano_adventurers()
|
||||
{
|
||||
using namespace VianoAdventurers;
|
||||
new npc_horde_adventurer_melee();
|
||||
}
|
||||
-956
@@ -1,956 +0,0 @@
|
||||
/*
|
||||
* Viano custom dungeon adventurers
|
||||
* Initial melee companion implementation.
|
||||
*/
|
||||
|
||||
#include "ScriptMgr.h"
|
||||
#include "ScriptedCreature.h"
|
||||
#include "ObjectAccessor.h"
|
||||
#include "Player.h"
|
||||
#include "Group.h"
|
||||
#include "TemporarySummon.h"
|
||||
#include "MotionMaster.h"
|
||||
#include "Log.h"
|
||||
|
||||
namespace VianoAdventurers
|
||||
{
|
||||
static constexpr float FOLLOW_DISTANCE = 2.5f;
|
||||
static constexpr float FOLLOW_ANGLE = 3.14159265f;
|
||||
static constexpr float TELEPORT_DISTANCE = 45.0f;
|
||||
static constexpr uint32 UPDATE_INTERVAL = 500;
|
||||
static constexpr uint32 DIAGNOSTIC_INTERVAL = 2000;
|
||||
|
||||
// VIANO_DD_PROFILE_V1_5
|
||||
enum AdventurerSpells : uint32
|
||||
{
|
||||
SPELL_ADVENTURER_MORTAL_STRIKE = 65926,
|
||||
SPELL_ADVENTURER_OVERPOWER = 65924,
|
||||
SPELL_ADVENTURER_BLADESTORM = 65947
|
||||
};
|
||||
|
||||
enum AdventurerEvents : uint32
|
||||
{
|
||||
EVENT_ADVENTURER_MORTAL_STRIKE = 1,
|
||||
EVENT_ADVENTURER_OVERPOWER,
|
||||
EVENT_ADVENTURER_BLADESTORM
|
||||
};
|
||||
|
||||
// VIANO_AI_DIAGNOSTICS_V1
|
||||
|
||||
class npc_horde_adventurer_melee : public CreatureScript
|
||||
{
|
||||
public:
|
||||
npc_horde_adventurer_melee() : CreatureScript("npc_horde_adventurer_melee") { }
|
||||
|
||||
struct npc_horde_adventurer_meleeAI : public ScriptedAI
|
||||
{
|
||||
npc_horde_adventurer_meleeAI(Creature* creature) : ScriptedAI(creature) { }
|
||||
|
||||
ObjectGuid ownerGuid;
|
||||
EventMap combatEvents;
|
||||
uint32 updateTimer = 0;
|
||||
uint32 diagnosticTimer = 0;
|
||||
uint32 recoveryTimer = 1000;
|
||||
Player* GetOwnerPlayer() const
|
||||
{
|
||||
if (!ownerGuid)
|
||||
return nullptr;
|
||||
|
||||
return ObjectAccessor::GetPlayer(*me, ownerGuid);
|
||||
}
|
||||
|
||||
void FollowOwner(Player* owner)
|
||||
{
|
||||
if (!owner)
|
||||
return;
|
||||
|
||||
me->GetMotionMaster()->MoveFollow(owner, FOLLOW_DISTANCE, FOLLOW_ANGLE);
|
||||
}
|
||||
|
||||
void ResetCombatEvents()
|
||||
{
|
||||
combatEvents.Reset();
|
||||
|
||||
combatEvents.ScheduleEvent(EVENT_ADVENTURER_MORTAL_STRIKE, 2 * IN_MILLISECONDS);
|
||||
combatEvents.ScheduleEvent(EVENT_ADVENTURER_OVERPOWER, 5 * IN_MILLISECONDS);
|
||||
combatEvents.ScheduleEvent(EVENT_ADVENTURER_BLADESTORM, 12 * IN_MILLISECONDS);
|
||||
}
|
||||
|
||||
void HandleCombatActions(uint32 diff)
|
||||
{
|
||||
Unit* victim = me->GetVictim();
|
||||
if (!victim || !victim->IsAlive())
|
||||
return;
|
||||
|
||||
combatEvents.Update(diff);
|
||||
|
||||
if (me->HasUnitState(UNIT_STATE_CASTING))
|
||||
return;
|
||||
|
||||
if (uint32 eventId = combatEvents.ExecuteEvent())
|
||||
{
|
||||
switch (eventId)
|
||||
{
|
||||
case EVENT_ADVENTURER_MORTAL_STRIKE:
|
||||
TC_LOG_INFO("scripts", "[VianoAI] cast Mortal Strike: target='%s'(%u)",
|
||||
UnitName(victim), UnitEntry(victim));
|
||||
DoCastVictim(SPELL_ADVENTURER_MORTAL_STRIKE);
|
||||
combatEvents.ScheduleEvent(EVENT_ADVENTURER_MORTAL_STRIKE,
|
||||
urand(6 * IN_MILLISECONDS, 8 * IN_MILLISECONDS));
|
||||
return;
|
||||
|
||||
case EVENT_ADVENTURER_OVERPOWER:
|
||||
TC_LOG_INFO("scripts", "[VianoAI] cast Overpower: target='%s'(%u)",
|
||||
UnitName(victim), UnitEntry(victim));
|
||||
DoCastVictim(SPELL_ADVENTURER_OVERPOWER);
|
||||
combatEvents.ScheduleEvent(EVENT_ADVENTURER_OVERPOWER,
|
||||
urand(10 * IN_MILLISECONDS, 14 * IN_MILLISECONDS));
|
||||
return;
|
||||
|
||||
case EVENT_ADVENTURER_BLADESTORM:
|
||||
TC_LOG_INFO("scripts", "[VianoAI] cast Bladestorm: target='%s'(%u)",
|
||||
UnitName(victim), UnitEntry(victim));
|
||||
DoCastVictim(SPELL_ADVENTURER_BLADESTORM);
|
||||
combatEvents.ScheduleEvent(EVENT_ADVENTURER_BLADESTORM,
|
||||
urand(30 * IN_MILLISECONDS, 45 * IN_MILLISECONDS));
|
||||
return;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
DoMeleeAttackIfReady();
|
||||
}
|
||||
void HandleOutOfCombatRecovery(uint32 diff)
|
||||
{
|
||||
if (!me->IsAlive())
|
||||
{
|
||||
recoveryTimer = 1000;
|
||||
return;
|
||||
}
|
||||
|
||||
// Keine künstliche Regeneration während eines Kampfes.
|
||||
if (me->IsInCombat())
|
||||
{
|
||||
recoveryTimer = 1000;
|
||||
return;
|
||||
}
|
||||
|
||||
if (recoveryTimer > diff)
|
||||
{
|
||||
recoveryTimer -= diff;
|
||||
return;
|
||||
}
|
||||
|
||||
recoveryTimer = 1000;
|
||||
|
||||
// 10 % der maximalen Lebenspunkte pro Sekunde.
|
||||
uint32 maxHealth = me->GetMaxHealth();
|
||||
|
||||
if (me->GetHealth() < maxHealth)
|
||||
{
|
||||
uint32 healthGain = maxHealth / 10;
|
||||
|
||||
if (healthGain == 0)
|
||||
healthGain = 1;
|
||||
|
||||
uint32 newHealth = me->GetHealth() + healthGain;
|
||||
|
||||
if (newHealth > maxHealth)
|
||||
newHealth = maxHealth;
|
||||
|
||||
me->SetHealth(newHealth);
|
||||
}
|
||||
|
||||
// 10 % des maximalen Manas pro Sekunde.
|
||||
uint32 maxMana = me->GetMaxPower(POWER_MANA);
|
||||
|
||||
if (maxMana > 0 && me->GetPower(POWER_MANA) < maxMana)
|
||||
{
|
||||
uint32 manaGain = maxMana / 10;
|
||||
|
||||
if (manaGain == 0)
|
||||
manaGain = 1;
|
||||
|
||||
uint32 newMana = me->GetPower(POWER_MANA) + manaGain;
|
||||
|
||||
if (newMana > maxMana)
|
||||
newMana = maxMana;
|
||||
|
||||
me->SetPower(POWER_MANA, newMana);
|
||||
}
|
||||
}
|
||||
|
||||
void InitializeAI() override
|
||||
{
|
||||
ScriptedAI::InitializeAI();
|
||||
|
||||
TempSummon* summon = me->ToTempSummon();
|
||||
if (!summon)
|
||||
{
|
||||
me->DespawnOrUnsummon();
|
||||
return;
|
||||
}
|
||||
|
||||
Unit* summoner = summon->GetSummoner();
|
||||
Player* owner = summoner ? summoner->ToPlayer() : nullptr;
|
||||
if (!owner)
|
||||
{
|
||||
me->DespawnOrUnsummon();
|
||||
return;
|
||||
}
|
||||
|
||||
ownerGuid = owner->GetGUID();
|
||||
|
||||
me->SetOwnerGUID(ownerGuid);
|
||||
me->SetFlag(UNIT_FIELD_FLAGS, UNIT_FLAG_PLAYER_CONTROLLED);
|
||||
me->SetFaction(owner->GetFaction());
|
||||
me->SetLevel(owner->getLevel());
|
||||
me->UpdateLevelDependantStats();
|
||||
|
||||
|
||||
static constexpr uint32 HORDE_ADVENTURER_MODELS[] =
|
||||
{
|
||||
30063,
|
||||
30064,
|
||||
30065,
|
||||
30066
|
||||
};
|
||||
uint32 displayId = HORDE_ADVENTURER_MODELS[urand(0, 3)];
|
||||
|
||||
me->SetDisplayId(displayId, true);
|
||||
|
||||
TC_LOG_INFO("scripts","[VianoAI] Horde adventurer display selected: %u", displayId);
|
||||
|
||||
// REACT_PASSIVE lets AttackStart() briefly set a victim, but the
|
||||
// core subsequently drops that victim again. We already suppress
|
||||
// autonomous proximity aggro in MoveInLineOfSight(), so DEFENSIVE
|
||||
// keeps an explicitly assigned assist target without making the
|
||||
// adventurer independently pull nearby creatures.
|
||||
me->SetReactState(REACT_DEFENSIVE);
|
||||
|
||||
TC_LOG_INFO("scripts",
|
||||
"[VianoAI] initialized me='%s' entry=%u owner='%s' ownerGuid=%s meFaction=%u ownerFaction=%u level=%u",
|
||||
me->GetName().c_str(), me->GetEntry(), owner->GetName().c_str(), ownerGuid.ToString().c_str(),
|
||||
me->GetFaction(), owner->GetFaction(), uint32(me->getLevel()));
|
||||
|
||||
ResetCombatEvents();
|
||||
FollowOwner(owner);
|
||||
}
|
||||
|
||||
void Reset() override
|
||||
{
|
||||
ResetCombatEvents();
|
||||
|
||||
if (Player* owner = GetOwnerPlayer())
|
||||
FollowOwner(owner);
|
||||
}
|
||||
|
||||
void MoveInLineOfSight(Unit* /*who*/) override
|
||||
{
|
||||
}
|
||||
|
||||
char const* UnitName(Unit* unit) const
|
||||
{
|
||||
return unit ? unit->GetName().c_str() : "<none>";
|
||||
}
|
||||
|
||||
uint32 UnitEntry(Unit* unit) const
|
||||
{
|
||||
return unit ? unit->GetEntry() : 0;
|
||||
}
|
||||
|
||||
bool IsValidCandidate(Unit* unit) const
|
||||
{
|
||||
return unit && unit->IsAlive() && me->IsValidAttackTarget(unit);
|
||||
}
|
||||
|
||||
void LogDiagnostics(Player* owner)
|
||||
{
|
||||
if (!owner)
|
||||
return;
|
||||
|
||||
Unit* ownerVictim = owner->GetVictim();
|
||||
Unit* selected = owner->GetSelectedUnit();
|
||||
Unit* meVictim = me->GetVictim();
|
||||
|
||||
Unit* firstOwnerAttacker = nullptr;
|
||||
for (Unit* attacker : owner->getAttackers())
|
||||
{
|
||||
if (attacker)
|
||||
{
|
||||
firstOwnerAttacker = attacker;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
Unit* firstMeAttacker = nullptr;
|
||||
for (Unit* attacker : me->getAttackers())
|
||||
{
|
||||
if (attacker)
|
||||
{
|
||||
firstMeAttacker = attacker;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
TC_LOG_INFO("scripts",
|
||||
"[VianoAI] me='%s' entry=%u lvl=%u faction=%u react=%u alive=%u combat=%u hp=%u/%u victim='%s'(%u) attackers=%u distance=%.2f",
|
||||
me->GetName().c_str(), me->GetEntry(), uint32(me->getLevel()), me->GetFaction(), uint32(me->GetReactState()),
|
||||
me->IsAlive() ? 1u : 0u, me->IsInCombat() ? 1u : 0u, me->GetHealth(), me->GetMaxHealth(),
|
||||
UnitName(meVictim), UnitEntry(meVictim), uint32(me->getAttackers().size()), me->GetDistance(owner));
|
||||
|
||||
TC_LOG_INFO("scripts",
|
||||
"[VianoAI] owner='%s' lvl=%u faction=%u alive=%u combat=%u victim='%s'(%u) selected='%s'(%u) attackers=%u",
|
||||
owner->GetName().c_str(), uint32(owner->getLevel()), owner->GetFaction(), owner->IsAlive() ? 1u : 0u,
|
||||
owner->IsInCombat() ? 1u : 0u, UnitName(ownerVictim), UnitEntry(ownerVictim), UnitName(selected), UnitEntry(selected),
|
||||
uint32(owner->getAttackers().size()));
|
||||
|
||||
if (ownerVictim)
|
||||
TC_LOG_INFO("scripts", "[VianoAI] ownerVictim='%s' entry=%u faction=%u validForBot=%u botCanAttack=%u ownerCanAttack=%u victim='%s'(%u)",
|
||||
ownerVictim->GetName().c_str(), ownerVictim->GetEntry(), ownerVictim->GetFaction(), IsValidCandidate(ownerVictim) ? 1u : 0u,
|
||||
me->IsValidAttackTarget(ownerVictim) ? 1u : 0u, owner->IsValidAttackTarget(ownerVictim) ? 1u : 0u,
|
||||
UnitName(ownerVictim->GetVictim()), UnitEntry(ownerVictim->GetVictim()));
|
||||
|
||||
if (selected)
|
||||
TC_LOG_INFO("scripts", "[VianoAI] selected='%s' entry=%u faction=%u validForBot=%u botCanAttack=%u ownerCanAttack=%u victim='%s'(%u)",
|
||||
selected->GetName().c_str(), selected->GetEntry(), selected->GetFaction(), IsValidCandidate(selected) ? 1u : 0u,
|
||||
me->IsValidAttackTarget(selected) ? 1u : 0u, owner->IsValidAttackTarget(selected) ? 1u : 0u,
|
||||
UnitName(selected->GetVictim()), UnitEntry(selected->GetVictim()));
|
||||
|
||||
if (firstOwnerAttacker)
|
||||
TC_LOG_INFO("scripts", "[VianoAI] firstOwnerAttacker='%s' entry=%u faction=%u validForBot=%u botCanAttack=%u ownerCanAttack=%u victim='%s'(%u)",
|
||||
firstOwnerAttacker->GetName().c_str(), firstOwnerAttacker->GetEntry(), firstOwnerAttacker->GetFaction(), IsValidCandidate(firstOwnerAttacker) ? 1u : 0u,
|
||||
me->IsValidAttackTarget(firstOwnerAttacker) ? 1u : 0u, owner->IsValidAttackTarget(firstOwnerAttacker) ? 1u : 0u,
|
||||
UnitName(firstOwnerAttacker->GetVictim()), UnitEntry(firstOwnerAttacker->GetVictim()));
|
||||
|
||||
if (firstMeAttacker)
|
||||
TC_LOG_INFO("scripts", "[VianoAI] firstMeAttacker='%s' entry=%u faction=%u victim='%s'(%u)",
|
||||
firstMeAttacker->GetName().c_str(), firstMeAttacker->GetEntry(), firstMeAttacker->GetFaction(),
|
||||
UnitName(firstMeAttacker->GetVictim()), UnitEntry(firstMeAttacker->GetVictim()));
|
||||
}
|
||||
|
||||
bool IsActivelyEngagedInCombat() const
|
||||
{
|
||||
// IsInCombat() alone is intentionally not enough here: the Core
|
||||
// may keep the combat flag set after the bot has no live opponent.
|
||||
if (Unit* victim = me->GetVictim())
|
||||
if (IsValidCandidate(victim))
|
||||
return true;
|
||||
|
||||
for (Unit* attacker : me->getAttackers())
|
||||
if (IsValidCandidate(attacker))
|
||||
return true;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
Unit* SelectGroupCombatTarget(Player* owner)
|
||||
{
|
||||
if (!owner)
|
||||
return nullptr;
|
||||
|
||||
std::list<Unit*> partyMembers;
|
||||
owner->GetPartyMembers(partyMembers);
|
||||
|
||||
// In Vcore GetPartyMembers() returns living members of the same
|
||||
// party/subgroup on the same map, plus their living guardian pets.
|
||||
// An enemy attacking one of them is therefore already part of the
|
||||
// group's current fight.
|
||||
for (Unit* member : partyMembers)
|
||||
{
|
||||
if (!member || !member->IsAlive())
|
||||
continue;
|
||||
|
||||
for (Unit* attacker : member->getAttackers())
|
||||
if (IsValidCandidate(attacker))
|
||||
return attacker;
|
||||
}
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
Unit* SelectAssistTarget(Player* owner)
|
||||
{
|
||||
if (!owner)
|
||||
return nullptr;
|
||||
|
||||
// 1. An active owner victim is an explicit target instruction.
|
||||
if (Unit* target = owner->GetVictim())
|
||||
if (IsValidCandidate(target))
|
||||
return target;
|
||||
|
||||
// During an already running fight, an explicitly selected living
|
||||
// hostile target may also be treated as an instruction. Merely
|
||||
// selecting an enemy out of combat still must not pull it.
|
||||
if (owner->IsInCombat())
|
||||
{
|
||||
if (Unit* target = owner->GetSelectedUnit())
|
||||
if (IsValidCandidate(target))
|
||||
return target;
|
||||
}
|
||||
|
||||
// 2. Keep the bot's own valid current victim.
|
||||
if (Unit* target = me->GetVictim())
|
||||
if (IsValidCandidate(target))
|
||||
return target;
|
||||
|
||||
// 3. Continue the group's fight even if the owner is a healer and
|
||||
// currently targets a friendly tank or another party member.
|
||||
if (Unit* target = SelectGroupCombatTarget(owner))
|
||||
return target;
|
||||
|
||||
// 4. Finally, defend the bot itself if something is attacking it.
|
||||
for (Unit* attacker : me->getAttackers())
|
||||
if (IsValidCandidate(attacker))
|
||||
return attacker;
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
void UpdateAI(uint32 diff) override
|
||||
{
|
||||
HandleOutOfCombatRecovery(diff);
|
||||
if (diagnosticTimer > diff)
|
||||
diagnosticTimer -= diff;
|
||||
else
|
||||
diagnosticTimer = 0;
|
||||
|
||||
if (updateTimer > diff)
|
||||
{
|
||||
updateTimer -= diff;
|
||||
|
||||
// VIANO_TARGET_CONTROL_V1_4
|
||||
// Keep the explicitly assigned victim. UpdateVictim() may
|
||||
// select another creature from the threat list.
|
||||
if (me->GetVictim())
|
||||
HandleCombatActions(diff);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
updateTimer = UPDATE_INTERVAL;
|
||||
|
||||
Player* owner = GetOwnerPlayer();
|
||||
if (!owner || !owner->IsInWorld() || !me->IsInMap(owner))
|
||||
{
|
||||
TC_LOG_INFO("scripts", "[VianoAI] despawn: owner invalid or not on same map (ownerFound=%u)", owner ? 1u : 0u);
|
||||
me->DespawnOrUnsummon();
|
||||
return;
|
||||
}
|
||||
|
||||
if (diagnosticTimer == 0)
|
||||
{
|
||||
diagnosticTimer = DIAGNOSTIC_INTERVAL;
|
||||
LogDiagnostics(owner);
|
||||
}
|
||||
|
||||
if (me->getLevel() != owner->getLevel())
|
||||
{
|
||||
me->SetLevel(owner->getLevel());
|
||||
me->UpdateLevelDependantStats();
|
||||
}
|
||||
|
||||
if (me->GetDistance(owner) > TELEPORT_DISTANCE && !IsActivelyEngagedInCombat())
|
||||
{
|
||||
// Keep the recovery teleport for geometry/jump problems, even
|
||||
// if the group is already fighting elsewhere. Only the bot's
|
||||
// own active engagement blocks teleporting.
|
||||
me->GetMotionMaster()->Clear();
|
||||
|
||||
me->NearTeleportTo(
|
||||
owner->GetPositionX() + 1.5f,
|
||||
owner->GetPositionY(),
|
||||
owner->GetPositionZ(),
|
||||
owner->GetOrientation()
|
||||
);
|
||||
|
||||
FollowOwner(owner);
|
||||
return;
|
||||
}
|
||||
|
||||
Unit* desiredTarget = SelectAssistTarget(owner);
|
||||
|
||||
if (desiredTarget)
|
||||
{
|
||||
if (me->GetVictim() != desiredTarget)
|
||||
{
|
||||
TC_LOG_INFO("scripts",
|
||||
"[VianoAI] AttackStart requested: target='%s' entry=%u targetFaction=%u valid=%u meCombatBefore=%u",
|
||||
desiredTarget->GetName().c_str(), desiredTarget->GetEntry(), desiredTarget->GetFaction(),
|
||||
me->IsValidAttackTarget(desiredTarget) ? 1u : 0u, me->IsInCombat() ? 1u : 0u);
|
||||
|
||||
AttackStart(desiredTarget);
|
||||
|
||||
Unit* afterVictim = me->GetVictim();
|
||||
TC_LOG_INFO("scripts", "[VianoAI] AttackStart result: meCombatAfter=%u victim='%s'(%u)",
|
||||
me->IsInCombat() ? 1u : 0u, UnitName(afterVictim), UnitEntry(afterVictim));
|
||||
}
|
||||
}
|
||||
else if (!IsActivelyEngagedInCombat())
|
||||
{
|
||||
// A stale Core combat flag must not prevent recovery/following.
|
||||
// We deliberately do not clear the flag here; OOC regeneration
|
||||
// will be handled as a separate change.
|
||||
FollowOwner(owner);
|
||||
}
|
||||
|
||||
// VIANO_TARGET_CONTROL_V1_4
|
||||
// Keep the explicitly assigned victim. UpdateVictim() may
|
||||
// select another creature from the threat list.
|
||||
if (me->GetVictim())
|
||||
HandleCombatActions(diff);
|
||||
}
|
||||
};
|
||||
|
||||
CreatureAI* GetAI(Creature* creature) const override
|
||||
{
|
||||
return new npc_horde_adventurer_meleeAI(creature);
|
||||
}
|
||||
};
|
||||
|
||||
// VIANO_TANK_V1
|
||||
class npc_horde_adventurer_tank : public CreatureScript
|
||||
{
|
||||
public:
|
||||
npc_horde_adventurer_tank() : CreatureScript("npc_horde_adventurer_tank") { }
|
||||
|
||||
struct npc_horde_adventurer_tankAI : public ScriptedAI
|
||||
{
|
||||
npc_horde_adventurer_tankAI(Creature* creature) : ScriptedAI(creature) { }
|
||||
|
||||
static constexpr uint8 SKULL_ICON_ID = 7;
|
||||
static constexpr float AUTO_PICKUP_RADIUS = 10.0f;
|
||||
static constexpr uint32 TANK_UPDATE_INTERVAL = 500;
|
||||
static constexpr uint32 AOE_DAMAGE_INTERVAL = 6000;
|
||||
|
||||
ObjectGuid ownerGuid;
|
||||
uint32 updateTimer = 0;
|
||||
uint32 recoveryTimer = 1000;
|
||||
uint32 aoeDamageTimer = 3000;
|
||||
|
||||
Player* GetOwnerPlayer() const
|
||||
{
|
||||
if (!ownerGuid)
|
||||
return nullptr;
|
||||
|
||||
return ObjectAccessor::GetPlayer(*me, ownerGuid);
|
||||
}
|
||||
|
||||
bool IsValidTankTarget(Unit* unit) const
|
||||
{
|
||||
if (!unit || !unit->IsAlive())
|
||||
return false;
|
||||
|
||||
// Dungeon-role bot: do not turn this into a PvP player hunter.
|
||||
if (!unit->ToCreature())
|
||||
return false;
|
||||
|
||||
return me->IsValidAttackTarget(unit);
|
||||
}
|
||||
|
||||
bool IsActivelyEngagedInCombat() const
|
||||
{
|
||||
if (Unit* victim = me->GetVictim())
|
||||
if (victim->IsAlive() && me->IsValidAttackTarget(victim))
|
||||
return true;
|
||||
|
||||
for (Unit* attacker : me->getAttackers())
|
||||
if (attacker && attacker->IsAlive() && me->IsValidAttackTarget(attacker))
|
||||
return true;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
void FollowOwner(Player* owner)
|
||||
{
|
||||
if (!owner)
|
||||
return;
|
||||
|
||||
me->GetMotionMaster()->MoveFollow(owner, FOLLOW_DISTANCE, FOLLOW_ANGLE);
|
||||
}
|
||||
|
||||
void HandleOutOfCombatRecovery(uint32 diff)
|
||||
{
|
||||
if (!me->IsAlive() || IsActivelyEngagedInCombat())
|
||||
{
|
||||
recoveryTimer = 1000;
|
||||
return;
|
||||
}
|
||||
|
||||
if (recoveryTimer > diff)
|
||||
{
|
||||
recoveryTimer -= diff;
|
||||
return;
|
||||
}
|
||||
|
||||
recoveryTimer = 1000;
|
||||
|
||||
uint32 maxHealth = me->GetMaxHealth();
|
||||
if (me->GetHealth() < maxHealth)
|
||||
{
|
||||
uint32 gain = maxHealth / 10;
|
||||
if (!gain)
|
||||
gain = 1;
|
||||
|
||||
uint32 health = me->GetHealth() + gain;
|
||||
me->SetHealth(health > maxHealth ? maxHealth : health);
|
||||
}
|
||||
|
||||
uint32 maxMana = me->GetMaxPower(POWER_MANA);
|
||||
if (maxMana && me->GetPower(POWER_MANA) < maxMana)
|
||||
{
|
||||
uint32 gain = maxMana / 10;
|
||||
if (!gain)
|
||||
gain = 1;
|
||||
|
||||
uint32 mana = me->GetPower(POWER_MANA) + gain;
|
||||
me->SetPower(POWER_MANA, mana > maxMana ? maxMana : mana);
|
||||
}
|
||||
}
|
||||
|
||||
Unit* GetSkullTarget(Player* owner) const
|
||||
{
|
||||
if (!owner)
|
||||
return nullptr;
|
||||
|
||||
Group* group = owner->GetGroup();
|
||||
if (!group)
|
||||
return nullptr;
|
||||
|
||||
ObjectGuid guid = group->GetTargetIcon(SKULL_ICON_ID);
|
||||
if (!guid)
|
||||
return nullptr;
|
||||
|
||||
Unit* target = ObjectAccessor::GetUnit(*me, guid);
|
||||
return IsValidTankTarget(target) ? target : nullptr;
|
||||
}
|
||||
|
||||
Unit* FindGroupAttacker(Player* owner) const
|
||||
{
|
||||
if (!owner)
|
||||
return nullptr;
|
||||
|
||||
if (Group* group = owner->GetGroup())
|
||||
{
|
||||
for (GroupReference* ref = group->GetFirstMember(); ref; ref = ref->next())
|
||||
{
|
||||
Player* member = ref->GetSource();
|
||||
if (!member || !member->IsAlive() || !member->IsInMap(owner))
|
||||
continue;
|
||||
|
||||
for (Unit* attacker : member->getAttackers())
|
||||
if (IsValidTankTarget(attacker))
|
||||
return attacker;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
for (Unit* attacker : owner->getAttackers())
|
||||
if (IsValidTankTarget(attacker))
|
||||
return attacker;
|
||||
}
|
||||
|
||||
for (Unit* attacker : me->getAttackers())
|
||||
if (IsValidTankTarget(attacker))
|
||||
return attacker;
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
Unit* SelectPrimaryTarget(Player* owner)
|
||||
{
|
||||
if (!owner)
|
||||
return nullptr;
|
||||
|
||||
// Skull is the only explicit permission for the tank to initiate
|
||||
// a pull. It also overrides the current primary target mid-fight.
|
||||
if (Unit* skull = GetSkullTarget(owner))
|
||||
return skull;
|
||||
|
||||
// Existing combat takes precedence over player selection.
|
||||
if (Unit* victim = me->GetVictim())
|
||||
if (IsValidTankTarget(victim))
|
||||
return victim;
|
||||
|
||||
// Pick up anything already attacking the group.
|
||||
if (Unit* attacker = FindGroupAttacker(owner))
|
||||
return attacker;
|
||||
|
||||
// The owner already attacking something is also a valid combat signal.
|
||||
if (Unit* victim = owner->GetVictim())
|
||||
if (IsValidTankTarget(victim))
|
||||
return victim;
|
||||
|
||||
// GetVictim() is not always populated for players.
|
||||
if (owner->IsInCombat())
|
||||
if (Unit* selected = owner->GetSelectedUnit())
|
||||
if (IsValidTankTarget(selected))
|
||||
return selected;
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
bool IsSafeAutoPickup(Unit* enemy) const
|
||||
{
|
||||
return IsValidTankTarget(enemy)
|
||||
&& me->GetDistance(enemy) <= AUTO_PICKUP_RADIUS
|
||||
&& me->IsWithinLOSInMap(enemy);
|
||||
}
|
||||
|
||||
void ForceTankThreat(Unit* enemy)
|
||||
{
|
||||
if (!IsSafeAutoPickup(enemy))
|
||||
return;
|
||||
|
||||
ThreatManager& threat = enemy->GetThreatManager();
|
||||
|
||||
// Match the current highest threat, then exceed it by 35%.
|
||||
// This gives us taunt-like recovery without permanent Fixate.
|
||||
threat.MatchUnitThreatToHighestThreat(me);
|
||||
|
||||
float current = threat.GetThreat(me, true);
|
||||
float bonus = current * 0.35f;
|
||||
if (bonus < 1.0f)
|
||||
bonus = 1.0f;
|
||||
|
||||
threat.AddThreat(me, bonus, nullptr, true, true);
|
||||
}
|
||||
|
||||
void RecoverGroupThreat(Player* owner)
|
||||
{
|
||||
if (!owner || !IsActivelyEngagedInCombat())
|
||||
return;
|
||||
|
||||
// Strong single-target threat on the current primary victim.
|
||||
if (Unit* victim = me->GetVictim())
|
||||
ForceTankThreat(victim);
|
||||
|
||||
// AoE pickup: only enemies already attacking real group members,
|
||||
// and only inside the configured 10 yard + LOS safety envelope.
|
||||
if (Group* group = owner->GetGroup())
|
||||
{
|
||||
for (GroupReference* ref = group->GetFirstMember(); ref; ref = ref->next())
|
||||
{
|
||||
Player* member = ref->GetSource();
|
||||
if (!member || !member->IsAlive() || !member->IsInMap(owner))
|
||||
continue;
|
||||
|
||||
for (Unit* attacker : member->getAttackers())
|
||||
ForceTankThreat(attacker);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
for (Unit* attacker : owner->getAttackers())
|
||||
ForceTankThreat(attacker);
|
||||
}
|
||||
|
||||
for (Unit* attacker : me->getAttackers())
|
||||
ForceTankThreat(attacker);
|
||||
}
|
||||
|
||||
void DoTankAoeDamage(Player* owner)
|
||||
{
|
||||
if (!owner || !IsActivelyEngagedInCombat())
|
||||
return;
|
||||
|
||||
uint32 damage = uint32(me->getLevel()) * 2u;
|
||||
if (!damage)
|
||||
damage = 1;
|
||||
|
||||
auto hit = [this, damage](Unit* enemy)
|
||||
{
|
||||
if (!IsSafeAutoPickup(enemy))
|
||||
return;
|
||||
|
||||
Unit::DealDamage(
|
||||
me,
|
||||
enemy,
|
||||
damage,
|
||||
0,
|
||||
DIRECT_DAMAGE,
|
||||
SPELL_SCHOOL_MASK_NORMAL,
|
||||
nullptr,
|
||||
false);
|
||||
};
|
||||
|
||||
if (Group* group = owner->GetGroup())
|
||||
{
|
||||
for (GroupReference* ref = group->GetFirstMember(); ref; ref = ref->next())
|
||||
{
|
||||
Player* member = ref->GetSource();
|
||||
if (!member || !member->IsAlive() || !member->IsInMap(owner))
|
||||
continue;
|
||||
|
||||
for (Unit* attacker : member->getAttackers())
|
||||
hit(attacker);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
for (Unit* attacker : owner->getAttackers())
|
||||
hit(attacker);
|
||||
}
|
||||
|
||||
for (Unit* attacker : me->getAttackers())
|
||||
hit(attacker);
|
||||
}
|
||||
|
||||
void InitializeAI() override
|
||||
{
|
||||
ScriptedAI::InitializeAI();
|
||||
|
||||
TempSummon* summon = me->ToTempSummon();
|
||||
if (!summon)
|
||||
{
|
||||
me->DespawnOrUnsummon();
|
||||
return;
|
||||
}
|
||||
|
||||
Unit* summoner = summon->GetSummoner();
|
||||
Player* owner = summoner ? summoner->ToPlayer() : nullptr;
|
||||
if (!owner)
|
||||
{
|
||||
me->DespawnOrUnsummon();
|
||||
return;
|
||||
}
|
||||
|
||||
ownerGuid = owner->GetGUID();
|
||||
|
||||
me->SetOwnerGUID(ownerGuid);
|
||||
me->SetFlag(UNIT_FIELD_FLAGS, UNIT_FLAG_PLAYER_CONTROLLED);
|
||||
me->SetFaction(owner->GetFaction());
|
||||
me->SetLevel(owner->getLevel());
|
||||
me->UpdateLevelDependantStats();
|
||||
|
||||
static constexpr uint32 HORDE_ADVENTURER_MODELS[] =
|
||||
{
|
||||
30063,
|
||||
30064,
|
||||
30065,
|
||||
30066
|
||||
};
|
||||
|
||||
me->SetDisplayId(HORDE_ADVENTURER_MODELS[urand(0, 3)], true);
|
||||
me->SetReactState(REACT_DEFENSIVE);
|
||||
|
||||
FollowOwner(owner);
|
||||
|
||||
TC_LOG_INFO(
|
||||
"scripts",
|
||||
"[VianoTank] initialized entry=%u owner='%s' level=%u health=%u armor=%u",
|
||||
me->GetEntry(),
|
||||
owner->GetName().c_str(),
|
||||
uint32(me->getLevel()),
|
||||
me->GetMaxHealth(),
|
||||
me->GetArmor());
|
||||
}
|
||||
|
||||
void Reset() override
|
||||
{
|
||||
updateTimer = 0;
|
||||
recoveryTimer = 1000;
|
||||
aoeDamageTimer = 3000;
|
||||
|
||||
if (Player* owner = GetOwnerPlayer())
|
||||
if (!IsActivelyEngagedInCombat())
|
||||
FollowOwner(owner);
|
||||
}
|
||||
|
||||
void MoveInLineOfSight(Unit* /*who*/) override
|
||||
{
|
||||
// No autonomous proximity pulls.
|
||||
}
|
||||
|
||||
void UpdateAI(uint32 diff) override
|
||||
{
|
||||
HandleOutOfCombatRecovery(diff);
|
||||
|
||||
if (aoeDamageTimer > diff)
|
||||
aoeDamageTimer -= diff;
|
||||
else
|
||||
aoeDamageTimer = 0;
|
||||
|
||||
if (updateTimer > diff)
|
||||
{
|
||||
updateTimer -= diff;
|
||||
|
||||
if (me->GetVictim())
|
||||
DoMeleeAttackIfReady();
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
updateTimer = TANK_UPDATE_INTERVAL;
|
||||
|
||||
Player* owner = GetOwnerPlayer();
|
||||
if (!owner || !owner->IsInWorld() || !me->IsInMap(owner))
|
||||
{
|
||||
me->DespawnOrUnsummon();
|
||||
return;
|
||||
}
|
||||
|
||||
if (me->getLevel() != owner->getLevel())
|
||||
{
|
||||
me->SetLevel(owner->getLevel());
|
||||
me->UpdateLevelDependantStats();
|
||||
}
|
||||
|
||||
bool engaged = IsActivelyEngagedInCombat();
|
||||
|
||||
// Recovery teleport is strictly out-of-combat.
|
||||
if (!engaged && me->GetDistance(owner) > TELEPORT_DISTANCE)
|
||||
{
|
||||
me->GetMotionMaster()->Clear();
|
||||
|
||||
me->NearTeleportTo(
|
||||
owner->GetPositionX() + 1.5f,
|
||||
owner->GetPositionY(),
|
||||
owner->GetPositionZ(),
|
||||
owner->GetOrientation());
|
||||
|
||||
FollowOwner(owner);
|
||||
return;
|
||||
}
|
||||
|
||||
Unit* desiredTarget = SelectPrimaryTarget(owner);
|
||||
|
||||
if (desiredTarget && me->GetVictim() != desiredTarget)
|
||||
AttackStart(desiredTarget);
|
||||
|
||||
engaged = IsActivelyEngagedInCombat();
|
||||
|
||||
if (engaged)
|
||||
{
|
||||
RecoverGroupThreat(owner);
|
||||
|
||||
if (aoeDamageTimer == 0)
|
||||
{
|
||||
aoeDamageTimer = AOE_DAMAGE_INTERVAL;
|
||||
DoTankAoeDamage(owner);
|
||||
}
|
||||
|
||||
if (me->GetVictim())
|
||||
DoMeleeAttackIfReady();
|
||||
}
|
||||
else
|
||||
FollowOwner(owner);
|
||||
}
|
||||
};
|
||||
|
||||
CreatureAI* GetAI(Creature* creature) const override
|
||||
{
|
||||
return new npc_horde_adventurer_tankAI(creature);
|
||||
}
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
void AddSC_viano_adventurers()
|
||||
{
|
||||
using namespace VianoAdventurers;
|
||||
new npc_horde_adventurer_melee();
|
||||
new npc_horde_adventurer_tank();
|
||||
}
|
||||
-964
@@ -1,964 +0,0 @@
|
||||
/*
|
||||
* Viano custom dungeon adventurers
|
||||
* Initial melee companion implementation.
|
||||
*/
|
||||
|
||||
#include "ScriptMgr.h"
|
||||
#include "ScriptedCreature.h"
|
||||
#include "ObjectAccessor.h"
|
||||
#include "Player.h"
|
||||
#include "Group.h"
|
||||
#include "TemporarySummon.h"
|
||||
#include "MotionMaster.h"
|
||||
#include "Log.h"
|
||||
#include <algorithm>
|
||||
#include <vector>
|
||||
|
||||
namespace VianoAdventurers
|
||||
{
|
||||
static constexpr float FOLLOW_DISTANCE = 2.5f;
|
||||
static constexpr float FOLLOW_ANGLE = 3.14159265f;
|
||||
static constexpr float TELEPORT_DISTANCE = 45.0f;
|
||||
static constexpr uint32 UPDATE_INTERVAL = 500;
|
||||
static constexpr uint32 DIAGNOSTIC_INTERVAL = 2000;
|
||||
|
||||
// VIANO_DD_PROFILE_V1_5
|
||||
enum AdventurerSpells : uint32
|
||||
{
|
||||
SPELL_ADVENTURER_MORTAL_STRIKE = 65926,
|
||||
SPELL_ADVENTURER_OVERPOWER = 65924,
|
||||
SPELL_ADVENTURER_BLADESTORM = 65947
|
||||
};
|
||||
|
||||
enum AdventurerEvents : uint32
|
||||
{
|
||||
EVENT_ADVENTURER_MORTAL_STRIKE = 1,
|
||||
EVENT_ADVENTURER_OVERPOWER,
|
||||
EVENT_ADVENTURER_BLADESTORM
|
||||
};
|
||||
|
||||
// VIANO_AI_DIAGNOSTICS_V1
|
||||
|
||||
class npc_horde_adventurer_melee : public CreatureScript
|
||||
{
|
||||
public:
|
||||
npc_horde_adventurer_melee() : CreatureScript("npc_horde_adventurer_melee") { }
|
||||
|
||||
struct npc_horde_adventurer_meleeAI : public ScriptedAI
|
||||
{
|
||||
npc_horde_adventurer_meleeAI(Creature* creature) : ScriptedAI(creature) { }
|
||||
|
||||
ObjectGuid ownerGuid;
|
||||
EventMap combatEvents;
|
||||
uint32 updateTimer = 0;
|
||||
uint32 diagnosticTimer = 0;
|
||||
uint32 recoveryTimer = 1000;
|
||||
Player* GetOwnerPlayer() const
|
||||
{
|
||||
if (!ownerGuid)
|
||||
return nullptr;
|
||||
|
||||
return ObjectAccessor::GetPlayer(*me, ownerGuid);
|
||||
}
|
||||
|
||||
void FollowOwner(Player* owner)
|
||||
{
|
||||
if (!owner)
|
||||
return;
|
||||
|
||||
me->GetMotionMaster()->MoveFollow(owner, FOLLOW_DISTANCE, FOLLOW_ANGLE);
|
||||
}
|
||||
|
||||
void ResetCombatEvents()
|
||||
{
|
||||
combatEvents.Reset();
|
||||
|
||||
combatEvents.ScheduleEvent(EVENT_ADVENTURER_MORTAL_STRIKE, 2 * IN_MILLISECONDS);
|
||||
combatEvents.ScheduleEvent(EVENT_ADVENTURER_OVERPOWER, 5 * IN_MILLISECONDS);
|
||||
combatEvents.ScheduleEvent(EVENT_ADVENTURER_BLADESTORM, 12 * IN_MILLISECONDS);
|
||||
}
|
||||
|
||||
void HandleCombatActions(uint32 diff)
|
||||
{
|
||||
Unit* victim = me->GetVictim();
|
||||
if (!victim || !victim->IsAlive())
|
||||
return;
|
||||
|
||||
combatEvents.Update(diff);
|
||||
|
||||
if (me->HasUnitState(UNIT_STATE_CASTING))
|
||||
return;
|
||||
|
||||
if (uint32 eventId = combatEvents.ExecuteEvent())
|
||||
{
|
||||
switch (eventId)
|
||||
{
|
||||
case EVENT_ADVENTURER_MORTAL_STRIKE:
|
||||
TC_LOG_INFO("scripts", "[VianoAI] cast Mortal Strike: target='%s'(%u)",
|
||||
UnitName(victim), UnitEntry(victim));
|
||||
DoCastVictim(SPELL_ADVENTURER_MORTAL_STRIKE);
|
||||
combatEvents.ScheduleEvent(EVENT_ADVENTURER_MORTAL_STRIKE,
|
||||
urand(6 * IN_MILLISECONDS, 8 * IN_MILLISECONDS));
|
||||
return;
|
||||
|
||||
case EVENT_ADVENTURER_OVERPOWER:
|
||||
TC_LOG_INFO("scripts", "[VianoAI] cast Overpower: target='%s'(%u)",
|
||||
UnitName(victim), UnitEntry(victim));
|
||||
DoCastVictim(SPELL_ADVENTURER_OVERPOWER);
|
||||
combatEvents.ScheduleEvent(EVENT_ADVENTURER_OVERPOWER,
|
||||
urand(10 * IN_MILLISECONDS, 14 * IN_MILLISECONDS));
|
||||
return;
|
||||
|
||||
case EVENT_ADVENTURER_BLADESTORM:
|
||||
TC_LOG_INFO("scripts", "[VianoAI] cast Bladestorm: target='%s'(%u)",
|
||||
UnitName(victim), UnitEntry(victim));
|
||||
DoCastVictim(SPELL_ADVENTURER_BLADESTORM);
|
||||
combatEvents.ScheduleEvent(EVENT_ADVENTURER_BLADESTORM,
|
||||
urand(30 * IN_MILLISECONDS, 45 * IN_MILLISECONDS));
|
||||
return;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
DoMeleeAttackIfReady();
|
||||
}
|
||||
void HandleOutOfCombatRecovery(uint32 diff)
|
||||
{
|
||||
if (!me->IsAlive())
|
||||
{
|
||||
recoveryTimer = 1000;
|
||||
return;
|
||||
}
|
||||
|
||||
// Keine künstliche Regeneration während eines Kampfes.
|
||||
if (me->IsInCombat())
|
||||
{
|
||||
recoveryTimer = 1000;
|
||||
return;
|
||||
}
|
||||
|
||||
if (recoveryTimer > diff)
|
||||
{
|
||||
recoveryTimer -= diff;
|
||||
return;
|
||||
}
|
||||
|
||||
recoveryTimer = 1000;
|
||||
|
||||
// 10 % der maximalen Lebenspunkte pro Sekunde.
|
||||
uint32 maxHealth = me->GetMaxHealth();
|
||||
|
||||
if (me->GetHealth() < maxHealth)
|
||||
{
|
||||
uint32 healthGain = maxHealth / 10;
|
||||
|
||||
if (healthGain == 0)
|
||||
healthGain = 1;
|
||||
|
||||
uint32 newHealth = me->GetHealth() + healthGain;
|
||||
|
||||
if (newHealth > maxHealth)
|
||||
newHealth = maxHealth;
|
||||
|
||||
me->SetHealth(newHealth);
|
||||
}
|
||||
|
||||
// 10 % des maximalen Manas pro Sekunde.
|
||||
uint32 maxMana = me->GetMaxPower(POWER_MANA);
|
||||
|
||||
if (maxMana > 0 && me->GetPower(POWER_MANA) < maxMana)
|
||||
{
|
||||
uint32 manaGain = maxMana / 10;
|
||||
|
||||
if (manaGain == 0)
|
||||
manaGain = 1;
|
||||
|
||||
uint32 newMana = me->GetPower(POWER_MANA) + manaGain;
|
||||
|
||||
if (newMana > maxMana)
|
||||
newMana = maxMana;
|
||||
|
||||
me->SetPower(POWER_MANA, newMana);
|
||||
}
|
||||
}
|
||||
|
||||
void InitializeAI() override
|
||||
{
|
||||
ScriptedAI::InitializeAI();
|
||||
|
||||
TempSummon* summon = me->ToTempSummon();
|
||||
if (!summon)
|
||||
{
|
||||
me->DespawnOrUnsummon();
|
||||
return;
|
||||
}
|
||||
|
||||
Unit* summoner = summon->GetSummoner();
|
||||
Player* owner = summoner ? summoner->ToPlayer() : nullptr;
|
||||
if (!owner)
|
||||
{
|
||||
me->DespawnOrUnsummon();
|
||||
return;
|
||||
}
|
||||
|
||||
ownerGuid = owner->GetGUID();
|
||||
|
||||
me->SetOwnerGUID(ownerGuid);
|
||||
me->SetFlag(UNIT_FIELD_FLAGS, UNIT_FLAG_PLAYER_CONTROLLED);
|
||||
me->SetFaction(owner->GetFaction());
|
||||
me->SetLevel(owner->getLevel());
|
||||
me->UpdateLevelDependantStats();
|
||||
|
||||
|
||||
static constexpr uint32 HORDE_ADVENTURER_MODELS[] =
|
||||
{
|
||||
30063,
|
||||
30064,
|
||||
30065,
|
||||
30066
|
||||
};
|
||||
uint32 displayId = HORDE_ADVENTURER_MODELS[urand(0, 3)];
|
||||
|
||||
me->SetDisplayId(displayId, true);
|
||||
|
||||
TC_LOG_INFO("scripts","[VianoAI] Horde adventurer display selected: %u", displayId);
|
||||
|
||||
// REACT_PASSIVE lets AttackStart() briefly set a victim, but the
|
||||
// core subsequently drops that victim again. We already suppress
|
||||
// autonomous proximity aggro in MoveInLineOfSight(), so DEFENSIVE
|
||||
// keeps an explicitly assigned assist target without making the
|
||||
// adventurer independently pull nearby creatures.
|
||||
me->SetReactState(REACT_DEFENSIVE);
|
||||
|
||||
TC_LOG_INFO("scripts",
|
||||
"[VianoAI] initialized me='%s' entry=%u owner='%s' ownerGuid=%s meFaction=%u ownerFaction=%u level=%u",
|
||||
me->GetName().c_str(), me->GetEntry(), owner->GetName().c_str(), ownerGuid.ToString().c_str(),
|
||||
me->GetFaction(), owner->GetFaction(), uint32(me->getLevel()));
|
||||
|
||||
ResetCombatEvents();
|
||||
FollowOwner(owner);
|
||||
}
|
||||
|
||||
void Reset() override
|
||||
{
|
||||
ResetCombatEvents();
|
||||
|
||||
if (Player* owner = GetOwnerPlayer())
|
||||
FollowOwner(owner);
|
||||
}
|
||||
|
||||
void MoveInLineOfSight(Unit* /*who*/) override
|
||||
{
|
||||
}
|
||||
|
||||
char const* UnitName(Unit* unit) const
|
||||
{
|
||||
return unit ? unit->GetName().c_str() : "<none>";
|
||||
}
|
||||
|
||||
uint32 UnitEntry(Unit* unit) const
|
||||
{
|
||||
return unit ? unit->GetEntry() : 0;
|
||||
}
|
||||
|
||||
bool IsValidCandidate(Unit* unit) const
|
||||
{
|
||||
return unit && unit->IsAlive() && me->IsValidAttackTarget(unit);
|
||||
}
|
||||
|
||||
void LogDiagnostics(Player* owner)
|
||||
{
|
||||
if (!owner)
|
||||
return;
|
||||
|
||||
Unit* ownerVictim = owner->GetVictim();
|
||||
Unit* selected = owner->GetSelectedUnit();
|
||||
Unit* meVictim = me->GetVictim();
|
||||
|
||||
Unit* firstOwnerAttacker = nullptr;
|
||||
for (Unit* attacker : owner->getAttackers())
|
||||
{
|
||||
if (attacker)
|
||||
{
|
||||
firstOwnerAttacker = attacker;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
Unit* firstMeAttacker = nullptr;
|
||||
for (Unit* attacker : me->getAttackers())
|
||||
{
|
||||
if (attacker)
|
||||
{
|
||||
firstMeAttacker = attacker;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
TC_LOG_INFO("scripts",
|
||||
"[VianoAI] me='%s' entry=%u lvl=%u faction=%u react=%u alive=%u combat=%u hp=%u/%u victim='%s'(%u) attackers=%u distance=%.2f",
|
||||
me->GetName().c_str(), me->GetEntry(), uint32(me->getLevel()), me->GetFaction(), uint32(me->GetReactState()),
|
||||
me->IsAlive() ? 1u : 0u, me->IsInCombat() ? 1u : 0u, me->GetHealth(), me->GetMaxHealth(),
|
||||
UnitName(meVictim), UnitEntry(meVictim), uint32(me->getAttackers().size()), me->GetDistance(owner));
|
||||
|
||||
TC_LOG_INFO("scripts",
|
||||
"[VianoAI] owner='%s' lvl=%u faction=%u alive=%u combat=%u victim='%s'(%u) selected='%s'(%u) attackers=%u",
|
||||
owner->GetName().c_str(), uint32(owner->getLevel()), owner->GetFaction(), owner->IsAlive() ? 1u : 0u,
|
||||
owner->IsInCombat() ? 1u : 0u, UnitName(ownerVictim), UnitEntry(ownerVictim), UnitName(selected), UnitEntry(selected),
|
||||
uint32(owner->getAttackers().size()));
|
||||
|
||||
if (ownerVictim)
|
||||
TC_LOG_INFO("scripts", "[VianoAI] ownerVictim='%s' entry=%u faction=%u validForBot=%u botCanAttack=%u ownerCanAttack=%u victim='%s'(%u)",
|
||||
ownerVictim->GetName().c_str(), ownerVictim->GetEntry(), ownerVictim->GetFaction(), IsValidCandidate(ownerVictim) ? 1u : 0u,
|
||||
me->IsValidAttackTarget(ownerVictim) ? 1u : 0u, owner->IsValidAttackTarget(ownerVictim) ? 1u : 0u,
|
||||
UnitName(ownerVictim->GetVictim()), UnitEntry(ownerVictim->GetVictim()));
|
||||
|
||||
if (selected)
|
||||
TC_LOG_INFO("scripts", "[VianoAI] selected='%s' entry=%u faction=%u validForBot=%u botCanAttack=%u ownerCanAttack=%u victim='%s'(%u)",
|
||||
selected->GetName().c_str(), selected->GetEntry(), selected->GetFaction(), IsValidCandidate(selected) ? 1u : 0u,
|
||||
me->IsValidAttackTarget(selected) ? 1u : 0u, owner->IsValidAttackTarget(selected) ? 1u : 0u,
|
||||
UnitName(selected->GetVictim()), UnitEntry(selected->GetVictim()));
|
||||
|
||||
if (firstOwnerAttacker)
|
||||
TC_LOG_INFO("scripts", "[VianoAI] firstOwnerAttacker='%s' entry=%u faction=%u validForBot=%u botCanAttack=%u ownerCanAttack=%u victim='%s'(%u)",
|
||||
firstOwnerAttacker->GetName().c_str(), firstOwnerAttacker->GetEntry(), firstOwnerAttacker->GetFaction(), IsValidCandidate(firstOwnerAttacker) ? 1u : 0u,
|
||||
me->IsValidAttackTarget(firstOwnerAttacker) ? 1u : 0u, owner->IsValidAttackTarget(firstOwnerAttacker) ? 1u : 0u,
|
||||
UnitName(firstOwnerAttacker->GetVictim()), UnitEntry(firstOwnerAttacker->GetVictim()));
|
||||
|
||||
if (firstMeAttacker)
|
||||
TC_LOG_INFO("scripts", "[VianoAI] firstMeAttacker='%s' entry=%u faction=%u victim='%s'(%u)",
|
||||
firstMeAttacker->GetName().c_str(), firstMeAttacker->GetEntry(), firstMeAttacker->GetFaction(),
|
||||
UnitName(firstMeAttacker->GetVictim()), UnitEntry(firstMeAttacker->GetVictim()));
|
||||
}
|
||||
|
||||
bool IsActivelyEngagedInCombat() const
|
||||
{
|
||||
// IsInCombat() alone is intentionally not enough here: the Core
|
||||
// may keep the combat flag set after the bot has no live opponent.
|
||||
if (Unit* victim = me->GetVictim())
|
||||
if (IsValidCandidate(victim))
|
||||
return true;
|
||||
|
||||
for (Unit* attacker : me->getAttackers())
|
||||
if (IsValidCandidate(attacker))
|
||||
return true;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
Unit* SelectGroupCombatTarget(Player* owner)
|
||||
{
|
||||
if (!owner)
|
||||
return nullptr;
|
||||
|
||||
std::list<Unit*> partyMembers;
|
||||
owner->GetPartyMembers(partyMembers);
|
||||
|
||||
// In Vcore GetPartyMembers() returns living members of the same
|
||||
// party/subgroup on the same map, plus their living guardian pets.
|
||||
// An enemy attacking one of them is therefore already part of the
|
||||
// group's current fight.
|
||||
for (Unit* member : partyMembers)
|
||||
{
|
||||
if (!member || !member->IsAlive())
|
||||
continue;
|
||||
|
||||
for (Unit* attacker : member->getAttackers())
|
||||
if (IsValidCandidate(attacker))
|
||||
return attacker;
|
||||
}
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
Unit* SelectAssistTarget(Player* owner)
|
||||
{
|
||||
if (!owner)
|
||||
return nullptr;
|
||||
|
||||
// 1. An active owner victim is an explicit target instruction.
|
||||
if (Unit* target = owner->GetVictim())
|
||||
if (IsValidCandidate(target))
|
||||
return target;
|
||||
|
||||
// During an already running fight, an explicitly selected living
|
||||
// hostile target may also be treated as an instruction. Merely
|
||||
// selecting an enemy out of combat still must not pull it.
|
||||
if (owner->IsInCombat())
|
||||
{
|
||||
if (Unit* target = owner->GetSelectedUnit())
|
||||
if (IsValidCandidate(target))
|
||||
return target;
|
||||
}
|
||||
|
||||
// 2. Keep the bot's own valid current victim.
|
||||
if (Unit* target = me->GetVictim())
|
||||
if (IsValidCandidate(target))
|
||||
return target;
|
||||
|
||||
// 3. Continue the group's fight even if the owner is a healer and
|
||||
// currently targets a friendly tank or another party member.
|
||||
if (Unit* target = SelectGroupCombatTarget(owner))
|
||||
return target;
|
||||
|
||||
// 4. Finally, defend the bot itself if something is attacking it.
|
||||
for (Unit* attacker : me->getAttackers())
|
||||
if (IsValidCandidate(attacker))
|
||||
return attacker;
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
void UpdateAI(uint32 diff) override
|
||||
{
|
||||
HandleOutOfCombatRecovery(diff);
|
||||
if (diagnosticTimer > diff)
|
||||
diagnosticTimer -= diff;
|
||||
else
|
||||
diagnosticTimer = 0;
|
||||
|
||||
if (updateTimer > diff)
|
||||
{
|
||||
updateTimer -= diff;
|
||||
|
||||
// VIANO_TARGET_CONTROL_V1_4
|
||||
// Keep the explicitly assigned victim. UpdateVictim() may
|
||||
// select another creature from the threat list.
|
||||
if (me->GetVictim())
|
||||
HandleCombatActions(diff);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
updateTimer = UPDATE_INTERVAL;
|
||||
|
||||
Player* owner = GetOwnerPlayer();
|
||||
if (!owner || !owner->IsInWorld() || !me->IsInMap(owner))
|
||||
{
|
||||
TC_LOG_INFO("scripts", "[VianoAI] despawn: owner invalid or not on same map (ownerFound=%u)", owner ? 1u : 0u);
|
||||
me->DespawnOrUnsummon();
|
||||
return;
|
||||
}
|
||||
|
||||
if (diagnosticTimer == 0)
|
||||
{
|
||||
diagnosticTimer = DIAGNOSTIC_INTERVAL;
|
||||
LogDiagnostics(owner);
|
||||
}
|
||||
|
||||
if (me->getLevel() != owner->getLevel())
|
||||
{
|
||||
me->SetLevel(owner->getLevel());
|
||||
me->UpdateLevelDependantStats();
|
||||
}
|
||||
|
||||
if (me->GetDistance(owner) > TELEPORT_DISTANCE && !IsActivelyEngagedInCombat())
|
||||
{
|
||||
// Keep the recovery teleport for geometry/jump problems, even
|
||||
// if the group is already fighting elsewhere. Only the bot's
|
||||
// own active engagement blocks teleporting.
|
||||
me->GetMotionMaster()->Clear();
|
||||
|
||||
me->NearTeleportTo(
|
||||
owner->GetPositionX() + 1.5f,
|
||||
owner->GetPositionY(),
|
||||
owner->GetPositionZ(),
|
||||
owner->GetOrientation()
|
||||
);
|
||||
|
||||
FollowOwner(owner);
|
||||
return;
|
||||
}
|
||||
|
||||
Unit* desiredTarget = SelectAssistTarget(owner);
|
||||
|
||||
if (desiredTarget)
|
||||
{
|
||||
if (me->GetVictim() != desiredTarget)
|
||||
{
|
||||
TC_LOG_INFO("scripts",
|
||||
"[VianoAI] AttackStart requested: target='%s' entry=%u targetFaction=%u valid=%u meCombatBefore=%u",
|
||||
desiredTarget->GetName().c_str(), desiredTarget->GetEntry(), desiredTarget->GetFaction(),
|
||||
me->IsValidAttackTarget(desiredTarget) ? 1u : 0u, me->IsInCombat() ? 1u : 0u);
|
||||
|
||||
AttackStart(desiredTarget);
|
||||
|
||||
Unit* afterVictim = me->GetVictim();
|
||||
TC_LOG_INFO("scripts", "[VianoAI] AttackStart result: meCombatAfter=%u victim='%s'(%u)",
|
||||
me->IsInCombat() ? 1u : 0u, UnitName(afterVictim), UnitEntry(afterVictim));
|
||||
}
|
||||
}
|
||||
else if (!IsActivelyEngagedInCombat())
|
||||
{
|
||||
// A stale Core combat flag must not prevent recovery/following.
|
||||
// We deliberately do not clear the flag here; OOC regeneration
|
||||
// will be handled as a separate change.
|
||||
FollowOwner(owner);
|
||||
}
|
||||
|
||||
// VIANO_TARGET_CONTROL_V1_4
|
||||
// Keep the explicitly assigned victim. UpdateVictim() may
|
||||
// select another creature from the threat list.
|
||||
if (me->GetVictim())
|
||||
HandleCombatActions(diff);
|
||||
}
|
||||
};
|
||||
|
||||
CreatureAI* GetAI(Creature* creature) const override
|
||||
{
|
||||
return new npc_horde_adventurer_meleeAI(creature);
|
||||
}
|
||||
};
|
||||
|
||||
// VIANO_TANK_V1
|
||||
class npc_horde_adventurer_tank : public CreatureScript
|
||||
{
|
||||
public:
|
||||
npc_horde_adventurer_tank() : CreatureScript("npc_horde_adventurer_tank") { }
|
||||
|
||||
struct npc_horde_adventurer_tankAI : public ScriptedAI
|
||||
{
|
||||
npc_horde_adventurer_tankAI(Creature* creature) : ScriptedAI(creature) { }
|
||||
|
||||
static constexpr uint8 SKULL_ICON_ID = 7;
|
||||
static constexpr float AUTO_PICKUP_RADIUS = 10.0f;
|
||||
static constexpr uint32 TANK_UPDATE_INTERVAL = 500;
|
||||
static constexpr uint32 AOE_DAMAGE_INTERVAL = 6000;
|
||||
|
||||
ObjectGuid ownerGuid;
|
||||
uint32 updateTimer = 0;
|
||||
uint32 recoveryTimer = 1000;
|
||||
uint32 aoeDamageTimer = 3000;
|
||||
|
||||
Player* GetOwnerPlayer() const
|
||||
{
|
||||
if (!ownerGuid)
|
||||
return nullptr;
|
||||
|
||||
return ObjectAccessor::GetPlayer(*me, ownerGuid);
|
||||
}
|
||||
|
||||
bool IsValidTankTarget(Unit* unit) const
|
||||
{
|
||||
if (!unit || !unit->IsAlive())
|
||||
return false;
|
||||
|
||||
// Dungeon-role bot: do not turn this into a PvP player hunter.
|
||||
if (!unit->ToCreature())
|
||||
return false;
|
||||
|
||||
return me->IsValidAttackTarget(unit);
|
||||
}
|
||||
|
||||
bool IsActivelyEngagedInCombat() const
|
||||
{
|
||||
if (Unit* victim = me->GetVictim())
|
||||
if (victim->IsAlive() && me->IsValidAttackTarget(victim))
|
||||
return true;
|
||||
|
||||
for (Unit* attacker : me->getAttackers())
|
||||
if (attacker && attacker->IsAlive() && me->IsValidAttackTarget(attacker))
|
||||
return true;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
void FollowOwner(Player* owner)
|
||||
{
|
||||
if (!owner)
|
||||
return;
|
||||
|
||||
me->GetMotionMaster()->MoveFollow(owner, FOLLOW_DISTANCE, FOLLOW_ANGLE);
|
||||
}
|
||||
|
||||
void HandleOutOfCombatRecovery(uint32 diff)
|
||||
{
|
||||
if (!me->IsAlive() || IsActivelyEngagedInCombat())
|
||||
{
|
||||
recoveryTimer = 1000;
|
||||
return;
|
||||
}
|
||||
|
||||
if (recoveryTimer > diff)
|
||||
{
|
||||
recoveryTimer -= diff;
|
||||
return;
|
||||
}
|
||||
|
||||
recoveryTimer = 1000;
|
||||
|
||||
uint32 maxHealth = me->GetMaxHealth();
|
||||
if (me->GetHealth() < maxHealth)
|
||||
{
|
||||
uint32 gain = maxHealth / 10;
|
||||
if (!gain)
|
||||
gain = 1;
|
||||
|
||||
uint32 health = me->GetHealth() + gain;
|
||||
me->SetHealth(health > maxHealth ? maxHealth : health);
|
||||
}
|
||||
|
||||
uint32 maxMana = me->GetMaxPower(POWER_MANA);
|
||||
if (maxMana && me->GetPower(POWER_MANA) < maxMana)
|
||||
{
|
||||
uint32 gain = maxMana / 10;
|
||||
if (!gain)
|
||||
gain = 1;
|
||||
|
||||
uint32 mana = me->GetPower(POWER_MANA) + gain;
|
||||
me->SetPower(POWER_MANA, mana > maxMana ? maxMana : mana);
|
||||
}
|
||||
}
|
||||
|
||||
Unit* GetSkullTarget(Player* owner) const
|
||||
{
|
||||
if (!owner)
|
||||
return nullptr;
|
||||
|
||||
Group* group = owner->GetGroup();
|
||||
if (!group)
|
||||
return nullptr;
|
||||
|
||||
ObjectGuid guid = group->GetTargetIcon(SKULL_ICON_ID);
|
||||
if (!guid)
|
||||
return nullptr;
|
||||
|
||||
Unit* target = ObjectAccessor::GetUnit(*me, guid);
|
||||
return IsValidTankTarget(target) ? target : nullptr;
|
||||
}
|
||||
|
||||
Unit* FindGroupAttacker(Player* owner) const
|
||||
{
|
||||
if (!owner)
|
||||
return nullptr;
|
||||
|
||||
if (Group* group = owner->GetGroup())
|
||||
{
|
||||
for (GroupReference* ref = group->GetFirstMember(); ref; ref = ref->next())
|
||||
{
|
||||
Player* member = ref->GetSource();
|
||||
if (!member || !member->IsAlive() || !member->IsInMap(owner))
|
||||
continue;
|
||||
|
||||
for (Unit* attacker : member->getAttackers())
|
||||
if (IsValidTankTarget(attacker))
|
||||
return attacker;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
for (Unit* attacker : owner->getAttackers())
|
||||
if (IsValidTankTarget(attacker))
|
||||
return attacker;
|
||||
}
|
||||
|
||||
for (Unit* attacker : me->getAttackers())
|
||||
if (IsValidTankTarget(attacker))
|
||||
return attacker;
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
Unit* SelectPrimaryTarget(Player* owner)
|
||||
{
|
||||
if (!owner)
|
||||
return nullptr;
|
||||
|
||||
// Skull is the only explicit permission for the tank to initiate
|
||||
// a pull. It also overrides the current primary target mid-fight.
|
||||
if (Unit* skull = GetSkullTarget(owner))
|
||||
return skull;
|
||||
|
||||
// Existing combat takes precedence over player selection.
|
||||
if (Unit* victim = me->GetVictim())
|
||||
if (IsValidTankTarget(victim))
|
||||
return victim;
|
||||
|
||||
// Pick up anything already attacking the group.
|
||||
if (Unit* attacker = FindGroupAttacker(owner))
|
||||
return attacker;
|
||||
|
||||
// The owner already attacking something is also a valid combat signal.
|
||||
if (Unit* victim = owner->GetVictim())
|
||||
if (IsValidTankTarget(victim))
|
||||
return victim;
|
||||
|
||||
// GetVictim() is not always populated for players.
|
||||
if (owner->IsInCombat())
|
||||
if (Unit* selected = owner->GetSelectedUnit())
|
||||
if (IsValidTankTarget(selected))
|
||||
return selected;
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
bool IsSafeAutoPickup(Unit* enemy) const
|
||||
{
|
||||
return IsValidTankTarget(enemy)
|
||||
&& me->GetDistance(enemy) <= AUTO_PICKUP_RADIUS
|
||||
&& me->IsWithinLOSInMap(enemy);
|
||||
}
|
||||
|
||||
void ForceTankThreat(Unit* enemy)
|
||||
{
|
||||
if (!IsSafeAutoPickup(enemy))
|
||||
return;
|
||||
|
||||
ThreatManager& threat = enemy->GetThreatManager();
|
||||
|
||||
// Match the current highest threat, then exceed it by 35%.
|
||||
// This gives us taunt-like recovery without permanent Fixate.
|
||||
threat.MatchUnitThreatToHighestThreat(me);
|
||||
|
||||
float current = threat.GetThreat(me, true);
|
||||
float bonus = current * 0.35f;
|
||||
if (bonus < 1.0f)
|
||||
bonus = 1.0f;
|
||||
|
||||
threat.AddThreat(me, bonus, nullptr, true, true);
|
||||
}
|
||||
|
||||
void CollectRelevantEnemyGuids(Player* owner, std::vector<ObjectGuid>& targets) const
|
||||
{
|
||||
if (!owner)
|
||||
return;
|
||||
|
||||
auto addTarget = [this, &targets](Unit* enemy)
|
||||
{
|
||||
if (!IsSafeAutoPickup(enemy))
|
||||
return;
|
||||
|
||||
ObjectGuid const guid = enemy->GetGUID();
|
||||
|
||||
if (std::find(targets.begin(), targets.end(), guid) == targets.end())
|
||||
targets.push_back(guid);
|
||||
};
|
||||
|
||||
if (Group* group = owner->GetGroup())
|
||||
{
|
||||
for (GroupReference* ref = group->GetFirstMember(); ref; ref = ref->next())
|
||||
{
|
||||
Player* member = ref->GetSource();
|
||||
if (!member || !member->IsAlive() || !member->IsInMap(owner))
|
||||
continue;
|
||||
|
||||
for (Unit* attacker : member->getAttackers())
|
||||
addTarget(attacker);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
for (Unit* attacker : owner->getAttackers())
|
||||
addTarget(attacker);
|
||||
}
|
||||
|
||||
for (Unit* attacker : me->getAttackers())
|
||||
addTarget(attacker);
|
||||
}
|
||||
|
||||
void RecoverGroupThreat(Player* owner)
|
||||
{
|
||||
if (!owner || !IsActivelyEngagedInCombat())
|
||||
return;
|
||||
|
||||
if (Unit* victim = me->GetVictim())
|
||||
ForceTankThreat(victim);
|
||||
|
||||
std::vector<ObjectGuid> targets;
|
||||
CollectRelevantEnemyGuids(owner, targets);
|
||||
|
||||
for (ObjectGuid const& guid : targets)
|
||||
{
|
||||
if (Unit* enemy = ObjectAccessor::GetUnit(*me, guid))
|
||||
ForceTankThreat(enemy);
|
||||
}
|
||||
}
|
||||
|
||||
void DoTankAoeDamage(Player* owner)
|
||||
{
|
||||
if (!owner || !IsActivelyEngagedInCombat())
|
||||
return;
|
||||
|
||||
uint32 damage = uint32(me->getLevel()) * 2u;
|
||||
if (!damage)
|
||||
damage = 1;
|
||||
|
||||
std::vector<ObjectGuid> targets;
|
||||
CollectRelevantEnemyGuids(owner, targets);
|
||||
|
||||
for (ObjectGuid const& guid : targets)
|
||||
{
|
||||
Unit* enemy = ObjectAccessor::GetUnit(*me, guid);
|
||||
if (!IsSafeAutoPickup(enemy))
|
||||
continue;
|
||||
|
||||
Unit::DealDamage(
|
||||
me,
|
||||
enemy,
|
||||
damage,
|
||||
0,
|
||||
DIRECT_DAMAGE,
|
||||
SPELL_SCHOOL_MASK_NORMAL,
|
||||
nullptr,
|
||||
false);
|
||||
}
|
||||
}
|
||||
|
||||
void InitializeAI() override
|
||||
{
|
||||
ScriptedAI::InitializeAI();
|
||||
|
||||
TempSummon* summon = me->ToTempSummon();
|
||||
if (!summon)
|
||||
{
|
||||
me->DespawnOrUnsummon();
|
||||
return;
|
||||
}
|
||||
|
||||
Unit* summoner = summon->GetSummoner();
|
||||
Player* owner = summoner ? summoner->ToPlayer() : nullptr;
|
||||
if (!owner)
|
||||
{
|
||||
me->DespawnOrUnsummon();
|
||||
return;
|
||||
}
|
||||
|
||||
ownerGuid = owner->GetGUID();
|
||||
|
||||
me->SetOwnerGUID(ownerGuid);
|
||||
me->SetFlag(UNIT_FIELD_FLAGS, UNIT_FLAG_PLAYER_CONTROLLED);
|
||||
me->SetFaction(owner->GetFaction());
|
||||
me->SetLevel(owner->getLevel());
|
||||
me->UpdateLevelDependantStats();
|
||||
|
||||
static constexpr uint32 HORDE_ADVENTURER_MODELS[] =
|
||||
{
|
||||
30063,
|
||||
30064,
|
||||
30065,
|
||||
30066
|
||||
};
|
||||
|
||||
me->SetDisplayId(HORDE_ADVENTURER_MODELS[urand(0, 3)], true);
|
||||
me->SetReactState(REACT_DEFENSIVE);
|
||||
|
||||
FollowOwner(owner);
|
||||
|
||||
TC_LOG_INFO(
|
||||
"scripts",
|
||||
"[VianoTank] initialized entry=%u owner='%s' level=%u health=%u armor=%u",
|
||||
me->GetEntry(),
|
||||
owner->GetName().c_str(),
|
||||
uint32(me->getLevel()),
|
||||
me->GetMaxHealth(),
|
||||
me->GetArmor());
|
||||
}
|
||||
|
||||
void Reset() override
|
||||
{
|
||||
updateTimer = 0;
|
||||
recoveryTimer = 1000;
|
||||
aoeDamageTimer = 3000;
|
||||
|
||||
if (Player* owner = GetOwnerPlayer())
|
||||
if (!IsActivelyEngagedInCombat())
|
||||
FollowOwner(owner);
|
||||
}
|
||||
|
||||
void MoveInLineOfSight(Unit* /*who*/) override
|
||||
{
|
||||
// No autonomous proximity pulls.
|
||||
}
|
||||
|
||||
void UpdateAI(uint32 diff) override
|
||||
{
|
||||
HandleOutOfCombatRecovery(diff);
|
||||
|
||||
if (aoeDamageTimer > diff)
|
||||
aoeDamageTimer -= diff;
|
||||
else
|
||||
aoeDamageTimer = 0;
|
||||
|
||||
if (updateTimer > diff)
|
||||
{
|
||||
updateTimer -= diff;
|
||||
|
||||
if (me->GetVictim())
|
||||
DoMeleeAttackIfReady();
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
updateTimer = TANK_UPDATE_INTERVAL;
|
||||
|
||||
Player* owner = GetOwnerPlayer();
|
||||
if (!owner || !owner->IsInWorld() || !me->IsInMap(owner))
|
||||
{
|
||||
me->DespawnOrUnsummon();
|
||||
return;
|
||||
}
|
||||
|
||||
if (me->getLevel() != owner->getLevel())
|
||||
{
|
||||
me->SetLevel(owner->getLevel());
|
||||
me->UpdateLevelDependantStats();
|
||||
}
|
||||
|
||||
bool engaged = IsActivelyEngagedInCombat();
|
||||
|
||||
// Recovery teleport is strictly out-of-combat.
|
||||
if (!engaged && me->GetDistance(owner) > TELEPORT_DISTANCE)
|
||||
{
|
||||
me->GetMotionMaster()->Clear();
|
||||
|
||||
me->NearTeleportTo(
|
||||
owner->GetPositionX() + 1.5f,
|
||||
owner->GetPositionY(),
|
||||
owner->GetPositionZ(),
|
||||
owner->GetOrientation());
|
||||
|
||||
FollowOwner(owner);
|
||||
return;
|
||||
}
|
||||
|
||||
Unit* desiredTarget = SelectPrimaryTarget(owner);
|
||||
|
||||
if (desiredTarget && me->GetVictim() != desiredTarget)
|
||||
AttackStart(desiredTarget);
|
||||
|
||||
engaged = IsActivelyEngagedInCombat();
|
||||
|
||||
if (engaged)
|
||||
{
|
||||
RecoverGroupThreat(owner);
|
||||
|
||||
if (aoeDamageTimer == 0)
|
||||
{
|
||||
aoeDamageTimer = AOE_DAMAGE_INTERVAL;
|
||||
DoTankAoeDamage(owner);
|
||||
}
|
||||
|
||||
if (me->GetVictim())
|
||||
DoMeleeAttackIfReady();
|
||||
}
|
||||
else
|
||||
FollowOwner(owner);
|
||||
}
|
||||
};
|
||||
|
||||
CreatureAI* GetAI(Creature* creature) const override
|
||||
{
|
||||
return new npc_horde_adventurer_tankAI(creature);
|
||||
}
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
void AddSC_viano_adventurers()
|
||||
{
|
||||
using namespace VianoAdventurers;
|
||||
new npc_horde_adventurer_melee();
|
||||
new npc_horde_adventurer_tank();
|
||||
}
|
||||
@@ -1,517 +0,0 @@
|
||||
/*
|
||||
* Viano custom dungeon adventurers
|
||||
* Initial melee companion implementation.
|
||||
*/
|
||||
|
||||
#include "ScriptMgr.h"
|
||||
#include "ScriptedCreature.h"
|
||||
#include "ObjectAccessor.h"
|
||||
#include "Player.h"
|
||||
#include "TemporarySummon.h"
|
||||
#include "MotionMaster.h"
|
||||
#include "Log.h"
|
||||
|
||||
namespace VianoAdventurers
|
||||
{
|
||||
static constexpr float FOLLOW_DISTANCE = 2.5f;
|
||||
static constexpr float FOLLOW_ANGLE = 3.14159265f;
|
||||
static constexpr float TELEPORT_DISTANCE = 45.0f;
|
||||
static constexpr uint32 UPDATE_INTERVAL = 500;
|
||||
static constexpr uint32 DIAGNOSTIC_INTERVAL = 2000;
|
||||
|
||||
// VIANO_DD_PROFILE_V1_5
|
||||
enum AdventurerSpells : uint32
|
||||
{
|
||||
SPELL_ADVENTURER_MORTAL_STRIKE = 65926,
|
||||
SPELL_ADVENTURER_OVERPOWER = 65924,
|
||||
SPELL_ADVENTURER_BLADESTORM = 65947
|
||||
};
|
||||
|
||||
enum AdventurerEvents : uint32
|
||||
{
|
||||
EVENT_ADVENTURER_MORTAL_STRIKE = 1,
|
||||
EVENT_ADVENTURER_OVERPOWER,
|
||||
EVENT_ADVENTURER_BLADESTORM
|
||||
};
|
||||
|
||||
// VIANO_AI_DIAGNOSTICS_V1
|
||||
|
||||
class npc_horde_adventurer_melee : public CreatureScript
|
||||
{
|
||||
public:
|
||||
npc_horde_adventurer_melee() : CreatureScript("npc_horde_adventurer_melee") { }
|
||||
|
||||
struct npc_horde_adventurer_meleeAI : public ScriptedAI
|
||||
{
|
||||
npc_horde_adventurer_meleeAI(Creature* creature) : ScriptedAI(creature) { }
|
||||
|
||||
ObjectGuid ownerGuid;
|
||||
EventMap combatEvents;
|
||||
uint32 updateTimer = 0;
|
||||
uint32 diagnosticTimer = 0;
|
||||
uint32 recoveryTimer = 1000;
|
||||
Player* GetOwnerPlayer() const
|
||||
{
|
||||
if (!ownerGuid)
|
||||
return nullptr;
|
||||
|
||||
return ObjectAccessor::GetPlayer(*me, ownerGuid);
|
||||
}
|
||||
|
||||
void FollowOwner(Player* owner)
|
||||
{
|
||||
if (!owner)
|
||||
return;
|
||||
|
||||
me->GetMotionMaster()->MoveFollow(owner, FOLLOW_DISTANCE, FOLLOW_ANGLE);
|
||||
}
|
||||
|
||||
void ResetCombatEvents()
|
||||
{
|
||||
combatEvents.Reset();
|
||||
|
||||
combatEvents.ScheduleEvent(EVENT_ADVENTURER_MORTAL_STRIKE, 2 * IN_MILLISECONDS);
|
||||
combatEvents.ScheduleEvent(EVENT_ADVENTURER_OVERPOWER, 5 * IN_MILLISECONDS);
|
||||
combatEvents.ScheduleEvent(EVENT_ADVENTURER_BLADESTORM, 12 * IN_MILLISECONDS);
|
||||
}
|
||||
|
||||
void HandleCombatActions(uint32 diff)
|
||||
{
|
||||
Unit* victim = me->GetVictim();
|
||||
if (!victim || !victim->IsAlive())
|
||||
return;
|
||||
|
||||
combatEvents.Update(diff);
|
||||
|
||||
if (me->HasUnitState(UNIT_STATE_CASTING))
|
||||
return;
|
||||
|
||||
if (uint32 eventId = combatEvents.ExecuteEvent())
|
||||
{
|
||||
switch (eventId)
|
||||
{
|
||||
case EVENT_ADVENTURER_MORTAL_STRIKE:
|
||||
TC_LOG_INFO("scripts", "[VianoAI] cast Mortal Strike: target='%s'(%u)",
|
||||
UnitName(victim), UnitEntry(victim));
|
||||
DoCastVictim(SPELL_ADVENTURER_MORTAL_STRIKE);
|
||||
combatEvents.ScheduleEvent(EVENT_ADVENTURER_MORTAL_STRIKE,
|
||||
urand(6 * IN_MILLISECONDS, 8 * IN_MILLISECONDS));
|
||||
return;
|
||||
|
||||
case EVENT_ADVENTURER_OVERPOWER:
|
||||
TC_LOG_INFO("scripts", "[VianoAI] cast Overpower: target='%s'(%u)",
|
||||
UnitName(victim), UnitEntry(victim));
|
||||
DoCastVictim(SPELL_ADVENTURER_OVERPOWER);
|
||||
combatEvents.ScheduleEvent(EVENT_ADVENTURER_OVERPOWER,
|
||||
urand(10 * IN_MILLISECONDS, 14 * IN_MILLISECONDS));
|
||||
return;
|
||||
|
||||
case EVENT_ADVENTURER_BLADESTORM:
|
||||
TC_LOG_INFO("scripts", "[VianoAI] cast Bladestorm: target='%s'(%u)",
|
||||
UnitName(victim), UnitEntry(victim));
|
||||
DoCastVictim(SPELL_ADVENTURER_BLADESTORM);
|
||||
combatEvents.ScheduleEvent(EVENT_ADVENTURER_BLADESTORM,
|
||||
urand(30 * IN_MILLISECONDS, 45 * IN_MILLISECONDS));
|
||||
return;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
DoMeleeAttackIfReady();
|
||||
}
|
||||
void HandleOutOfCombatRecovery(uint32 diff)
|
||||
{
|
||||
if (!me->IsAlive())
|
||||
{
|
||||
recoveryTimer = 1000;
|
||||
return;
|
||||
}
|
||||
|
||||
// Keine künstliche Regeneration während eines Kampfes.
|
||||
if (me->IsInCombat())
|
||||
{
|
||||
recoveryTimer = 1000;
|
||||
return;
|
||||
}
|
||||
|
||||
if (recoveryTimer > diff)
|
||||
{
|
||||
recoveryTimer -= diff;
|
||||
return;
|
||||
}
|
||||
|
||||
recoveryTimer = 1000;
|
||||
|
||||
// 10 % der maximalen Lebenspunkte pro Sekunde.
|
||||
uint32 maxHealth = me->GetMaxHealth();
|
||||
|
||||
if (me->GetHealth() < maxHealth)
|
||||
{
|
||||
uint32 healthGain = maxHealth / 10;
|
||||
|
||||
if (healthGain == 0)
|
||||
healthGain = 1;
|
||||
|
||||
uint32 newHealth = me->GetHealth() + healthGain;
|
||||
|
||||
if (newHealth > maxHealth)
|
||||
newHealth = maxHealth;
|
||||
|
||||
me->SetHealth(newHealth);
|
||||
}
|
||||
|
||||
// 10 % des maximalen Manas pro Sekunde.
|
||||
uint32 maxMana = me->GetMaxPower(POWER_MANA);
|
||||
|
||||
if (maxMana > 0 && me->GetPower(POWER_MANA) < maxMana)
|
||||
{
|
||||
uint32 manaGain = maxMana / 10;
|
||||
|
||||
if (manaGain == 0)
|
||||
manaGain = 1;
|
||||
|
||||
uint32 newMana = me->GetPower(POWER_MANA) + manaGain;
|
||||
|
||||
if (newMana > maxMana)
|
||||
newMana = maxMana;
|
||||
|
||||
me->SetPower(POWER_MANA, newMana);
|
||||
}
|
||||
}
|
||||
|
||||
void InitializeAI() override
|
||||
{
|
||||
ScriptedAI::InitializeAI();
|
||||
|
||||
TempSummon* summon = me->ToTempSummon();
|
||||
if (!summon)
|
||||
{
|
||||
me->DespawnOrUnsummon();
|
||||
return;
|
||||
}
|
||||
|
||||
Unit* summoner = summon->GetSummoner();
|
||||
Player* owner = summoner ? summoner->ToPlayer() : nullptr;
|
||||
if (!owner)
|
||||
{
|
||||
me->DespawnOrUnsummon();
|
||||
return;
|
||||
}
|
||||
|
||||
ownerGuid = owner->GetGUID();
|
||||
|
||||
me->SetOwnerGUID(ownerGuid);
|
||||
me->SetFlag(UNIT_FIELD_FLAGS, UNIT_FLAG_PLAYER_CONTROLLED);
|
||||
me->SetFaction(owner->GetFaction());
|
||||
me->SetLevel(owner->getLevel());
|
||||
me->UpdateLevelDependantStats();
|
||||
|
||||
|
||||
static constexpr uint32 HORDE_ADVENTURER_MODELS[] =
|
||||
{
|
||||
30063,
|
||||
30064,
|
||||
30065,
|
||||
30066
|
||||
};
|
||||
uint32 displayId = HORDE_ADVENTURER_MODELS[urand(0, 3)];
|
||||
|
||||
me->SetDisplayId(displayId, true);
|
||||
|
||||
TC_LOG_INFO("scripts","[VianoAI] Horde adventurer display selected: %u", displayId);
|
||||
|
||||
// REACT_PASSIVE lets AttackStart() briefly set a victim, but the
|
||||
// core subsequently drops that victim again. We already suppress
|
||||
// autonomous proximity aggro in MoveInLineOfSight(), so DEFENSIVE
|
||||
// keeps an explicitly assigned assist target without making the
|
||||
// adventurer independently pull nearby creatures.
|
||||
me->SetReactState(REACT_DEFENSIVE);
|
||||
|
||||
TC_LOG_INFO("scripts",
|
||||
"[VianoAI] initialized me='%s' entry=%u owner='%s' ownerGuid=%s meFaction=%u ownerFaction=%u level=%u",
|
||||
me->GetName().c_str(), me->GetEntry(), owner->GetName().c_str(), ownerGuid.ToString().c_str(),
|
||||
me->GetFaction(), owner->GetFaction(), uint32(me->getLevel()));
|
||||
|
||||
ResetCombatEvents();
|
||||
FollowOwner(owner);
|
||||
}
|
||||
|
||||
void Reset() override
|
||||
{
|
||||
ResetCombatEvents();
|
||||
|
||||
if (Player* owner = GetOwnerPlayer())
|
||||
FollowOwner(owner);
|
||||
}
|
||||
|
||||
void MoveInLineOfSight(Unit* /*who*/) override
|
||||
{
|
||||
}
|
||||
|
||||
char const* UnitName(Unit* unit) const
|
||||
{
|
||||
return unit ? unit->GetName().c_str() : "<none>";
|
||||
}
|
||||
|
||||
uint32 UnitEntry(Unit* unit) const
|
||||
{
|
||||
return unit ? unit->GetEntry() : 0;
|
||||
}
|
||||
|
||||
bool IsValidCandidate(Unit* unit) const
|
||||
{
|
||||
return unit && unit->IsAlive() && me->IsValidAttackTarget(unit);
|
||||
}
|
||||
|
||||
void LogDiagnostics(Player* owner)
|
||||
{
|
||||
if (!owner)
|
||||
return;
|
||||
|
||||
Unit* ownerVictim = owner->GetVictim();
|
||||
Unit* selected = owner->GetSelectedUnit();
|
||||
Unit* meVictim = me->GetVictim();
|
||||
|
||||
Unit* firstOwnerAttacker = nullptr;
|
||||
for (Unit* attacker : owner->getAttackers())
|
||||
{
|
||||
if (attacker)
|
||||
{
|
||||
firstOwnerAttacker = attacker;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
Unit* firstMeAttacker = nullptr;
|
||||
for (Unit* attacker : me->getAttackers())
|
||||
{
|
||||
if (attacker)
|
||||
{
|
||||
firstMeAttacker = attacker;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
TC_LOG_INFO("scripts",
|
||||
"[VianoAI] me='%s' entry=%u lvl=%u faction=%u react=%u alive=%u combat=%u hp=%u/%u victim='%s'(%u) attackers=%u distance=%.2f",
|
||||
me->GetName().c_str(), me->GetEntry(), uint32(me->getLevel()), me->GetFaction(), uint32(me->GetReactState()),
|
||||
me->IsAlive() ? 1u : 0u, me->IsInCombat() ? 1u : 0u, me->GetHealth(), me->GetMaxHealth(),
|
||||
UnitName(meVictim), UnitEntry(meVictim), uint32(me->getAttackers().size()), me->GetDistance(owner));
|
||||
|
||||
TC_LOG_INFO("scripts",
|
||||
"[VianoAI] owner='%s' lvl=%u faction=%u alive=%u combat=%u victim='%s'(%u) selected='%s'(%u) attackers=%u",
|
||||
owner->GetName().c_str(), uint32(owner->getLevel()), owner->GetFaction(), owner->IsAlive() ? 1u : 0u,
|
||||
owner->IsInCombat() ? 1u : 0u, UnitName(ownerVictim), UnitEntry(ownerVictim), UnitName(selected), UnitEntry(selected),
|
||||
uint32(owner->getAttackers().size()));
|
||||
|
||||
if (ownerVictim)
|
||||
TC_LOG_INFO("scripts", "[VianoAI] ownerVictim='%s' entry=%u faction=%u validForBot=%u botCanAttack=%u ownerCanAttack=%u victim='%s'(%u)",
|
||||
ownerVictim->GetName().c_str(), ownerVictim->GetEntry(), ownerVictim->GetFaction(), IsValidCandidate(ownerVictim) ? 1u : 0u,
|
||||
me->IsValidAttackTarget(ownerVictim) ? 1u : 0u, owner->IsValidAttackTarget(ownerVictim) ? 1u : 0u,
|
||||
UnitName(ownerVictim->GetVictim()), UnitEntry(ownerVictim->GetVictim()));
|
||||
|
||||
if (selected)
|
||||
TC_LOG_INFO("scripts", "[VianoAI] selected='%s' entry=%u faction=%u validForBot=%u botCanAttack=%u ownerCanAttack=%u victim='%s'(%u)",
|
||||
selected->GetName().c_str(), selected->GetEntry(), selected->GetFaction(), IsValidCandidate(selected) ? 1u : 0u,
|
||||
me->IsValidAttackTarget(selected) ? 1u : 0u, owner->IsValidAttackTarget(selected) ? 1u : 0u,
|
||||
UnitName(selected->GetVictim()), UnitEntry(selected->GetVictim()));
|
||||
|
||||
if (firstOwnerAttacker)
|
||||
TC_LOG_INFO("scripts", "[VianoAI] firstOwnerAttacker='%s' entry=%u faction=%u validForBot=%u botCanAttack=%u ownerCanAttack=%u victim='%s'(%u)",
|
||||
firstOwnerAttacker->GetName().c_str(), firstOwnerAttacker->GetEntry(), firstOwnerAttacker->GetFaction(), IsValidCandidate(firstOwnerAttacker) ? 1u : 0u,
|
||||
me->IsValidAttackTarget(firstOwnerAttacker) ? 1u : 0u, owner->IsValidAttackTarget(firstOwnerAttacker) ? 1u : 0u,
|
||||
UnitName(firstOwnerAttacker->GetVictim()), UnitEntry(firstOwnerAttacker->GetVictim()));
|
||||
|
||||
if (firstMeAttacker)
|
||||
TC_LOG_INFO("scripts", "[VianoAI] firstMeAttacker='%s' entry=%u faction=%u victim='%s'(%u)",
|
||||
firstMeAttacker->GetName().c_str(), firstMeAttacker->GetEntry(), firstMeAttacker->GetFaction(),
|
||||
UnitName(firstMeAttacker->GetVictim()), UnitEntry(firstMeAttacker->GetVictim()));
|
||||
}
|
||||
|
||||
bool IsActivelyEngagedInCombat() const
|
||||
{
|
||||
// IsInCombat() alone is intentionally not enough here: the Core
|
||||
// may keep the combat flag set after the bot has no live opponent.
|
||||
if (Unit* victim = me->GetVictim())
|
||||
if (IsValidCandidate(victim))
|
||||
return true;
|
||||
|
||||
for (Unit* attacker : me->getAttackers())
|
||||
if (IsValidCandidate(attacker))
|
||||
return true;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
Unit* SelectGroupCombatTarget(Player* owner)
|
||||
{
|
||||
if (!owner)
|
||||
return nullptr;
|
||||
|
||||
std::list<Unit*> partyMembers;
|
||||
owner->GetPartyMembers(partyMembers);
|
||||
|
||||
// In Vcore GetPartyMembers() returns living members of the same
|
||||
// party/subgroup on the same map, plus their living guardian pets.
|
||||
// An enemy attacking one of them is therefore already part of the
|
||||
// group's current fight.
|
||||
for (Unit* member : partyMembers)
|
||||
{
|
||||
if (!member || !member->IsAlive())
|
||||
continue;
|
||||
|
||||
for (Unit* attacker : member->getAttackers())
|
||||
if (IsValidCandidate(attacker))
|
||||
return attacker;
|
||||
}
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
Unit* SelectAssistTarget(Player* owner)
|
||||
{
|
||||
if (!owner)
|
||||
return nullptr;
|
||||
|
||||
// 1. An active owner victim is an explicit target instruction.
|
||||
if (Unit* target = owner->GetVictim())
|
||||
if (IsValidCandidate(target))
|
||||
return target;
|
||||
|
||||
// During an already running fight, an explicitly selected living
|
||||
// hostile target may also be treated as an instruction. Merely
|
||||
// selecting an enemy out of combat still must not pull it.
|
||||
if (owner->IsInCombat())
|
||||
{
|
||||
if (Unit* target = owner->GetSelectedUnit())
|
||||
if (IsValidCandidate(target))
|
||||
return target;
|
||||
}
|
||||
|
||||
// 2. Keep the bot's own valid current victim.
|
||||
if (Unit* target = me->GetVictim())
|
||||
if (IsValidCandidate(target))
|
||||
return target;
|
||||
|
||||
// 3. Continue the group's fight even if the owner is a healer and
|
||||
// currently targets a friendly tank or another party member.
|
||||
if (Unit* target = SelectGroupCombatTarget(owner))
|
||||
return target;
|
||||
|
||||
// 4. Finally, defend the bot itself if something is attacking it.
|
||||
for (Unit* attacker : me->getAttackers())
|
||||
if (IsValidCandidate(attacker))
|
||||
return attacker;
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
void UpdateAI(uint32 diff) override
|
||||
{
|
||||
HandleOutOfCombatRecovery(diff);
|
||||
if (diagnosticTimer > diff)
|
||||
diagnosticTimer -= diff;
|
||||
else
|
||||
diagnosticTimer = 0;
|
||||
|
||||
if (updateTimer > diff)
|
||||
{
|
||||
updateTimer -= diff;
|
||||
|
||||
// VIANO_TARGET_CONTROL_V1_4
|
||||
// Keep the explicitly assigned victim. UpdateVictim() may
|
||||
// select another creature from the threat list.
|
||||
if (me->GetVictim())
|
||||
HandleCombatActions(diff);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
updateTimer = UPDATE_INTERVAL;
|
||||
|
||||
Player* owner = GetOwnerPlayer();
|
||||
if (!owner || !owner->IsInWorld() || !me->IsInMap(owner))
|
||||
{
|
||||
TC_LOG_INFO("scripts", "[VianoAI] despawn: owner invalid or not on same map (ownerFound=%u)", owner ? 1u : 0u);
|
||||
me->DespawnOrUnsummon();
|
||||
return;
|
||||
}
|
||||
|
||||
if (diagnosticTimer == 0)
|
||||
{
|
||||
diagnosticTimer = DIAGNOSTIC_INTERVAL;
|
||||
LogDiagnostics(owner);
|
||||
}
|
||||
|
||||
if (me->getLevel() != owner->getLevel())
|
||||
{
|
||||
me->SetLevel(owner->getLevel());
|
||||
me->UpdateLevelDependantStats();
|
||||
}
|
||||
|
||||
if (me->GetDistance(owner) > TELEPORT_DISTANCE && !IsActivelyEngagedInCombat())
|
||||
{
|
||||
// Keep the recovery teleport for geometry/jump problems, even
|
||||
// if the group is already fighting elsewhere. Only the bot's
|
||||
// own active engagement blocks teleporting.
|
||||
me->GetMotionMaster()->Clear();
|
||||
|
||||
me->NearTeleportTo(
|
||||
owner->GetPositionX() + 1.5f,
|
||||
owner->GetPositionY(),
|
||||
owner->GetPositionZ(),
|
||||
owner->GetOrientation()
|
||||
);
|
||||
|
||||
FollowOwner(owner);
|
||||
return;
|
||||
}
|
||||
|
||||
Unit* desiredTarget = SelectAssistTarget(owner);
|
||||
|
||||
if (desiredTarget)
|
||||
{
|
||||
if (me->GetVictim() != desiredTarget)
|
||||
{
|
||||
TC_LOG_INFO("scripts",
|
||||
"[VianoAI] AttackStart requested: target='%s' entry=%u targetFaction=%u valid=%u meCombatBefore=%u",
|
||||
desiredTarget->GetName().c_str(), desiredTarget->GetEntry(), desiredTarget->GetFaction(),
|
||||
me->IsValidAttackTarget(desiredTarget) ? 1u : 0u, me->IsInCombat() ? 1u : 0u);
|
||||
|
||||
AttackStart(desiredTarget);
|
||||
|
||||
Unit* afterVictim = me->GetVictim();
|
||||
TC_LOG_INFO("scripts", "[VianoAI] AttackStart result: meCombatAfter=%u victim='%s'(%u)",
|
||||
me->IsInCombat() ? 1u : 0u, UnitName(afterVictim), UnitEntry(afterVictim));
|
||||
}
|
||||
}
|
||||
else if (!IsActivelyEngagedInCombat())
|
||||
{
|
||||
// A stale Core combat flag must not prevent recovery/following.
|
||||
// We deliberately do not clear the flag here; OOC regeneration
|
||||
// will be handled as a separate change.
|
||||
FollowOwner(owner);
|
||||
}
|
||||
|
||||
// VIANO_TARGET_CONTROL_V1_4
|
||||
// Keep the explicitly assigned victim. UpdateVictim() may
|
||||
// select another creature from the threat list.
|
||||
if (me->GetVictim())
|
||||
HandleCombatActions(diff);
|
||||
}
|
||||
};
|
||||
|
||||
CreatureAI* GetAI(Creature* creature) const override
|
||||
{
|
||||
return new npc_horde_adventurer_meleeAI(creature);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
void AddSC_viano_adventurers()
|
||||
{
|
||||
using namespace VianoAdventurers;
|
||||
new npc_horde_adventurer_melee();
|
||||
}
|
||||
@@ -12,9 +12,11 @@
|
||||
#include "ScriptMgr.h"
|
||||
#include "Chat.h"
|
||||
#include "Creature.h"
|
||||
#include "Group.h"
|
||||
#include "ObjectAccessor.h"
|
||||
#include "ObjectMgr.h"
|
||||
#include "Player.h"
|
||||
#include "SpellAuras.h"
|
||||
#include "RBAC.h"
|
||||
#include "TemporarySummon.h"
|
||||
#include "Unit.h"
|
||||
@@ -403,6 +405,8 @@ namespace VianoBotCommands
|
||||
entry = 900001;
|
||||
else if (role == "tank")
|
||||
entry = 900002;
|
||||
else if (role == "healer")
|
||||
entry = 900003;
|
||||
else
|
||||
{
|
||||
handler->SendSysMessage("Unknown role. Usage: .viano bot summon melee|tank");
|
||||
@@ -433,6 +437,140 @@ namespace VianoBotCommands
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
// VIANO_GROUP_DIAGNOSTICS_V1
|
||||
static void PrintAuraSummary(ChatHandler* handler, char const* label, Unit* unit)
|
||||
{
|
||||
if (!handler || !unit)
|
||||
return;
|
||||
|
||||
Unit::AuraApplicationMap const& auras = unit->GetAppliedAuras();
|
||||
handler->PSendSysMessage("%s appliedAuras=%u", label, uint32(auras.size()));
|
||||
|
||||
uint32 shown = 0;
|
||||
for (auto const& pair : auras)
|
||||
{
|
||||
AuraApplication const* app = pair.second;
|
||||
if (!app || !app->GetBase())
|
||||
continue;
|
||||
|
||||
handler->PSendSysMessage(
|
||||
"%s aura[%u] spell=%u positive=%s caster=%s",
|
||||
label,
|
||||
shown,
|
||||
app->GetBase()->GetId(),
|
||||
BoolText(app->IsPositive()),
|
||||
GuidText(app->GetBase()->GetCasterGUID()).c_str());
|
||||
|
||||
if (++shown >= 20)
|
||||
{
|
||||
handler->SendSysMessage("aura output truncated after 20 entries.");
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static bool HandleGroup(ChatHandler* handler, char const* /*args*/)
|
||||
{
|
||||
Creature* bot = GetSelectedVianoBot(handler);
|
||||
if (!bot)
|
||||
return true;
|
||||
|
||||
Player* owner = bot->GetOwner() ? bot->GetOwner()->ToPlayer() : nullptr;
|
||||
if (!owner)
|
||||
{
|
||||
handler->SendSysMessage("Selected bot has no resolved player owner.");
|
||||
return true;
|
||||
}
|
||||
|
||||
PrintHeader(handler, bot, "group");
|
||||
|
||||
std::list<Unit*> units;
|
||||
owner->GetPartyMembers(units);
|
||||
handler->PSendSysMessage(
|
||||
"owner='%s' effectiveGroupSize=%u enumeratedUnits=%u",
|
||||
owner->GetName().c_str(),
|
||||
owner->GetVianoEffectiveGroupSize(),
|
||||
uint32(units.size()));
|
||||
|
||||
uint32 index = 0;
|
||||
for (Unit* unit : units)
|
||||
{
|
||||
if (!unit)
|
||||
continue;
|
||||
|
||||
handler->PSendSysMessage(
|
||||
"member[%u] name='%s' entry=%u guid=%s player=%s viano=%s alive=%s distance=%.2f",
|
||||
index++,
|
||||
UnitName(unit),
|
||||
UnitEntry(unit),
|
||||
GuidText(unit->GetGUID()).c_str(),
|
||||
BoolText(unit->IsPlayer()),
|
||||
BoolText(unit->IsVianoAdventurer()),
|
||||
BoolText(unit->IsAlive()),
|
||||
bot->GetDistance(unit));
|
||||
}
|
||||
|
||||
if (Group* group = owner->GetGroup())
|
||||
{
|
||||
handler->SendSysMessage("targetIconSource=real-group");
|
||||
for (uint8 icon = 0; icon < TARGETICONCOUNT; ++icon)
|
||||
{
|
||||
ObjectGuid guid = group->GetTargetIcon(icon);
|
||||
Unit* target = guid ? ObjectAccessor::GetUnit(*bot, guid) : nullptr;
|
||||
handler->PSendSysMessage(
|
||||
"targetIcon[%u] guid=%s target='%s' entry=%u",
|
||||
uint32(icon),
|
||||
GuidText(guid).c_str(),
|
||||
UnitName(target),
|
||||
UnitEntry(target));
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
handler->SendSysMessage("targetIconSource=viano-virtual");
|
||||
for (uint8 icon = 0; icon < TARGETICONCOUNT; ++icon)
|
||||
{
|
||||
ObjectGuid guid = owner->GetVianoTargetIcon(icon);
|
||||
Unit* target = guid ? ObjectAccessor::GetUnit(*bot, guid) : nullptr;
|
||||
handler->PSendSysMessage(
|
||||
"targetIcon[%u] guid=%s target='%s' entry=%u",
|
||||
uint32(icon),
|
||||
GuidText(guid).c_str(),
|
||||
UnitName(target),
|
||||
UnitEntry(target));
|
||||
}
|
||||
}
|
||||
|
||||
PrintAuraSummary(handler, "owner", owner);
|
||||
PrintAuraSummary(handler, "bot", bot);
|
||||
|
||||
uint32 missingShown = 0;
|
||||
for (auto const& pair : owner->GetAppliedAuras())
|
||||
{
|
||||
AuraApplication const* app = pair.second;
|
||||
if (!app || !app->GetBase() || !app->IsPositive())
|
||||
continue;
|
||||
|
||||
uint32 spellId = app->GetBase()->GetId();
|
||||
if (bot->HasAura(spellId))
|
||||
continue;
|
||||
|
||||
handler->PSendSysMessage(
|
||||
"ownerPositiveMissingOnBot spell=%u caster=%s",
|
||||
spellId,
|
||||
GuidText(app->GetBase()->GetCasterGUID()).c_str());
|
||||
|
||||
if (++missingShown >= 20)
|
||||
{
|
||||
handler->SendSysMessage("missing-aura output truncated after 20 entries.");
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
class VianoBotCommandScript : public CommandScript
|
||||
{
|
||||
public:
|
||||
@@ -447,6 +585,7 @@ namespace VianoBotCommands
|
||||
{ "combat", rbac::RBAC_PERM_COMMAND_GM, false, &HandleCombat, "" },
|
||||
{ "owner", rbac::RBAC_PERM_COMMAND_GM, false, &HandleOwner, "" },
|
||||
{ "role", rbac::RBAC_PERM_COMMAND_GM, false, &HandleRole, "" },
|
||||
{ "group", rbac::RBAC_PERM_COMMAND_GM, false, &HandleGroup, "" },
|
||||
{ "summon", rbac::RBAC_PERM_COMMAND_GM, false, &HandleSummon, "" },
|
||||
};
|
||||
|
||||
|
||||
@@ -1,400 +0,0 @@
|
||||
/*
|
||||
* Viano bot GM diagnostics.
|
||||
*
|
||||
* Read-only commands:
|
||||
* .viano bot info
|
||||
* .viano bot relations
|
||||
* .viano bot combat
|
||||
* .viano bot owner
|
||||
* .viano bot role
|
||||
*/
|
||||
|
||||
#include "ScriptMgr.h"
|
||||
#include "Chat.h"
|
||||
#include "Creature.h"
|
||||
#include "ObjectAccessor.h"
|
||||
#include "ObjectMgr.h"
|
||||
#include "Player.h"
|
||||
#include "TemporarySummon.h"
|
||||
#include "Unit.h"
|
||||
|
||||
#include <string>
|
||||
|
||||
namespace VianoBotCommands
|
||||
{
|
||||
static char const* BoolText(bool value)
|
||||
{
|
||||
return value ? "yes" : "no";
|
||||
}
|
||||
|
||||
static std::string GuidText(ObjectGuid const& guid)
|
||||
{
|
||||
return guid ? guid.ToString() : std::string("<empty>");
|
||||
}
|
||||
|
||||
static char const* UnitName(Unit const* unit)
|
||||
{
|
||||
return unit ? unit->GetName().c_str() : "<none>";
|
||||
}
|
||||
|
||||
static uint32 UnitEntry(Unit const* unit)
|
||||
{
|
||||
if (Creature const* creature = unit ? unit->ToCreature() : nullptr)
|
||||
return creature->GetEntry();
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static std::string GetScriptName(Creature const* creature)
|
||||
{
|
||||
if (!creature)
|
||||
return {};
|
||||
|
||||
if (CreatureTemplate const* creatureTemplate = creature->GetCreatureTemplate())
|
||||
return sObjectMgr->GetScriptName(creatureTemplate->ScriptID);
|
||||
|
||||
return {};
|
||||
}
|
||||
|
||||
static bool IsVianoBot(Creature const* creature)
|
||||
{
|
||||
if (!creature)
|
||||
return false;
|
||||
|
||||
std::string const scriptName = GetScriptName(creature);
|
||||
|
||||
return scriptName.rfind("npc_horde_adventurer_", 0) == 0
|
||||
|| scriptName.rfind("npc_alliance_adventurer_", 0) == 0;
|
||||
}
|
||||
|
||||
static Creature* GetSelectedVianoBot(ChatHandler* handler)
|
||||
{
|
||||
if (!handler || !handler->GetSession())
|
||||
return nullptr;
|
||||
|
||||
Player* player = handler->GetSession()->GetPlayer();
|
||||
if (!player)
|
||||
return nullptr;
|
||||
|
||||
Unit* selected = player->GetSelectedUnit();
|
||||
Creature* creature = selected ? selected->ToCreature() : nullptr;
|
||||
|
||||
if (!creature)
|
||||
{
|
||||
handler->SendSysMessage("Select a Viano bot creature first.");
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
if (!IsVianoBot(creature))
|
||||
{
|
||||
handler->PSendSysMessage(
|
||||
"Selected creature is not a recognized Viano bot. entry=%u script='%s'",
|
||||
creature->GetEntry(), GetScriptName(creature).c_str());
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
return creature;
|
||||
}
|
||||
|
||||
static Player* GetCommandPlayer(ChatHandler* handler)
|
||||
{
|
||||
return handler && handler->GetSession() ? handler->GetSession()->GetPlayer() : nullptr;
|
||||
}
|
||||
|
||||
static Unit* GetResolvedController(Creature* bot)
|
||||
{
|
||||
return bot ? bot->GetCharmerOrOwnerOrCreatorOrSelf() : nullptr;
|
||||
}
|
||||
|
||||
static char const* DetectRole(Creature const* bot)
|
||||
{
|
||||
std::string const scriptName = GetScriptName(bot);
|
||||
|
||||
if (scriptName.find("_melee") != std::string::npos)
|
||||
return "damage-melee";
|
||||
if (scriptName.find("_tank") != std::string::npos)
|
||||
return "tank";
|
||||
if (scriptName.find("_healer") != std::string::npos)
|
||||
return "healer";
|
||||
if (scriptName.find("_ranged") != std::string::npos)
|
||||
return "damage-ranged";
|
||||
|
||||
return "unknown";
|
||||
}
|
||||
|
||||
static void PrintHeader(ChatHandler* handler, Creature* bot, char const* section)
|
||||
{
|
||||
handler->PSendSysMessage(
|
||||
"[VianoBot:%s] name='%s' entry=%u guid=%s script='%s'",
|
||||
section,
|
||||
bot->GetName().c_str(),
|
||||
bot->GetEntry(),
|
||||
GuidText(bot->GetGUID()).c_str(),
|
||||
GetScriptName(bot).c_str());
|
||||
}
|
||||
|
||||
static bool HandleInfo(ChatHandler* handler, char const* /*args*/)
|
||||
{
|
||||
Creature* bot = GetSelectedVianoBot(handler);
|
||||
if (!bot)
|
||||
return true;
|
||||
|
||||
Player* gm = GetCommandPlayer(handler);
|
||||
Unit* victim = bot->GetVictim();
|
||||
|
||||
PrintHeader(handler, bot, "info");
|
||||
|
||||
handler->PSendSysMessage(
|
||||
"level=%u alive=%s health=%u/%u faction=%u react=%u combat=%s attackers=%u",
|
||||
uint32(bot->getLevel()),
|
||||
BoolText(bot->IsAlive()),
|
||||
bot->GetHealth(),
|
||||
bot->GetMaxHealth(),
|
||||
bot->GetFaction(),
|
||||
uint32(bot->GetReactState()),
|
||||
BoolText(bot->IsInCombat()),
|
||||
uint32(bot->getAttackers().size()));
|
||||
|
||||
handler->PSendSysMessage(
|
||||
"unitFlags=0x%08X immuneToPC=%s immuneToNPC=%s selectable=%s",
|
||||
bot->GetUInt32Value(UNIT_FIELD_FLAGS),
|
||||
BoolText(bot->IsImmuneToPC()),
|
||||
BoolText(bot->IsImmuneToNPC()),
|
||||
BoolText(!bot->HasFlag(UNIT_FIELD_FLAGS, UNIT_FLAG_NOT_SELECTABLE)));
|
||||
|
||||
handler->PSendSysMessage(
|
||||
"victim='%s' entry=%u victimGuid=%s",
|
||||
UnitName(victim),
|
||||
UnitEntry(victim),
|
||||
victim ? GuidText(victim->GetGUID()).c_str() : "<empty>");
|
||||
|
||||
if (gm)
|
||||
handler->PSendSysMessage(
|
||||
"distanceToGM=%.2f gmAssistTarget=%s gmAttackTarget=%s",
|
||||
bot->GetDistance(gm),
|
||||
BoolText(gm->IsValidAssistTarget(bot)),
|
||||
BoolText(gm->IsValidAttackTarget(bot)));
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool HandleRelations(ChatHandler* handler, char const* /*args*/)
|
||||
{
|
||||
Creature* bot = GetSelectedVianoBot(handler);
|
||||
if (!bot)
|
||||
return true;
|
||||
|
||||
Player* player = GetCommandPlayer(handler);
|
||||
if (!player)
|
||||
return true;
|
||||
|
||||
PrintHeader(handler, bot, "relations");
|
||||
|
||||
handler->PSendSysMessage(
|
||||
"player='%s' playerGuid=%s botFaction=%u playerFaction=%u",
|
||||
player->GetName().c_str(),
|
||||
GuidText(player->GetGUID()).c_str(),
|
||||
bot->GetFaction(),
|
||||
player->GetFaction());
|
||||
|
||||
handler->PSendSysMessage(
|
||||
"player->bot: party=%s raid=%s assist=%s attack=%s reaction=%d",
|
||||
BoolText(player->IsInPartyWith(bot)),
|
||||
BoolText(player->IsInRaidWith(bot)),
|
||||
BoolText(player->IsValidAssistTarget(bot)),
|
||||
BoolText(player->IsValidAttackTarget(bot)),
|
||||
int32(player->GetReactionTo(bot)));
|
||||
|
||||
handler->PSendSysMessage(
|
||||
"bot->player: party=%s raid=%s assist=%s attack=%s reaction=%d",
|
||||
BoolText(bot->IsInPartyWith(player)),
|
||||
BoolText(bot->IsInRaidWith(player)),
|
||||
BoolText(bot->IsValidAssistTarget(player)),
|
||||
BoolText(bot->IsValidAttackTarget(player)),
|
||||
int32(bot->GetReactionTo(player)));
|
||||
|
||||
handler->PSendSysMessage(
|
||||
"bot immuneToPC=%s immuneToNPC=%s unitFlags=0x%08X",
|
||||
BoolText(bot->IsImmuneToPC()),
|
||||
BoolText(bot->IsImmuneToNPC()),
|
||||
bot->GetUInt32Value(UNIT_FIELD_FLAGS));
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool HandleCombat(ChatHandler* handler, char const* /*args*/)
|
||||
{
|
||||
Creature* bot = GetSelectedVianoBot(handler);
|
||||
if (!bot)
|
||||
return true;
|
||||
|
||||
Player* player = GetCommandPlayer(handler);
|
||||
Unit* victim = bot->GetVictim();
|
||||
|
||||
PrintHeader(handler, bot, "combat");
|
||||
|
||||
handler->PSendSysMessage(
|
||||
"botCombat=%s victim='%s' entry=%u attackers=%u casting=%s",
|
||||
BoolText(bot->IsInCombat()),
|
||||
UnitName(victim),
|
||||
UnitEntry(victim),
|
||||
uint32(bot->getAttackers().size()),
|
||||
BoolText(bot->HasUnitState(UNIT_STATE_CASTING)));
|
||||
|
||||
uint32 index = 0;
|
||||
for (Unit* attacker : bot->getAttackers())
|
||||
{
|
||||
if (!attacker)
|
||||
continue;
|
||||
|
||||
handler->PSendSysMessage(
|
||||
"attacker[%u]='%s' entry=%u guid=%s alive=%s victim='%s'(%u)",
|
||||
index++,
|
||||
attacker->GetName().c_str(),
|
||||
UnitEntry(attacker),
|
||||
GuidText(attacker->GetGUID()).c_str(),
|
||||
BoolText(attacker->IsAlive()),
|
||||
UnitName(attacker->GetVictim()),
|
||||
UnitEntry(attacker->GetVictim()));
|
||||
}
|
||||
|
||||
if (index == 0)
|
||||
handler->SendSysMessage("attackers=<none>");
|
||||
|
||||
if (player)
|
||||
{
|
||||
Unit* playerVictim = player->GetVictim();
|
||||
Unit* selected = player->GetSelectedUnit();
|
||||
|
||||
handler->PSendSysMessage(
|
||||
"playerCombat=%s playerVictim='%s'(%u) selected='%s'(%u)",
|
||||
BoolText(player->IsInCombat()),
|
||||
UnitName(playerVictim),
|
||||
UnitEntry(playerVictim),
|
||||
UnitName(selected),
|
||||
UnitEntry(selected));
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool HandleOwner(ChatHandler* handler, char const* /*args*/)
|
||||
{
|
||||
Creature* bot = GetSelectedVianoBot(handler);
|
||||
if (!bot)
|
||||
return true;
|
||||
|
||||
Unit* resolved = GetResolvedController(bot);
|
||||
Unit* owner = bot->GetOwner();
|
||||
Unit* creator = bot->GetCreatorGUID()
|
||||
? ObjectAccessor::GetUnit(*bot, bot->GetCreatorGUID())
|
||||
: nullptr;
|
||||
Unit* charmer = bot->GetCharmer();
|
||||
|
||||
PrintHeader(handler, bot, "owner");
|
||||
|
||||
handler->PSendSysMessage(
|
||||
"OwnerGUID=%s CreatorGUID=%s CharmerGUID=%s",
|
||||
GuidText(bot->GetOwnerGUID()).c_str(),
|
||||
GuidText(bot->GetCreatorGUID()).c_str(),
|
||||
GuidText(bot->GetCharmerGUID()).c_str());
|
||||
|
||||
handler->PSendSysMessage(
|
||||
"owner='%s'(%u) creator='%s'(%u) charmer='%s'(%u)",
|
||||
UnitName(owner), UnitEntry(owner),
|
||||
UnitName(creator), UnitEntry(creator),
|
||||
UnitName(charmer), UnitEntry(charmer));
|
||||
|
||||
handler->PSendSysMessage(
|
||||
"resolvedController='%s' entry=%u guid=%s typeId=%u",
|
||||
UnitName(resolved),
|
||||
UnitEntry(resolved),
|
||||
resolved ? GuidText(resolved->GetGUID()).c_str() : "<empty>",
|
||||
resolved ? uint32(resolved->GetTypeId()) : 0u);
|
||||
|
||||
if (TempSummon* summon = bot->ToTempSummon())
|
||||
{
|
||||
Unit* summoner = summon->GetSummoner();
|
||||
handler->PSendSysMessage(
|
||||
"tempSummon=yes summoner='%s' entry=%u guid=%s",
|
||||
UnitName(summoner),
|
||||
UnitEntry(summoner),
|
||||
summoner ? GuidText(summoner->GetGUID()).c_str() : "<empty>");
|
||||
}
|
||||
else
|
||||
handler->SendSysMessage("tempSummon=no");
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool HandleRole(ChatHandler* handler, char const* /*args*/)
|
||||
{
|
||||
Creature* bot = GetSelectedVianoBot(handler);
|
||||
if (!bot)
|
||||
return true;
|
||||
|
||||
Player* player = GetCommandPlayer(handler);
|
||||
Unit* victim = bot->GetVictim();
|
||||
|
||||
PrintHeader(handler, bot, "role");
|
||||
|
||||
handler->PSendSysMessage(
|
||||
"role=%s react=%u level=%u combat=%s victim='%s'(%u)",
|
||||
DetectRole(bot),
|
||||
uint32(bot->GetReactState()),
|
||||
uint32(bot->getLevel()),
|
||||
BoolText(bot->IsInCombat()),
|
||||
UnitName(victim),
|
||||
UnitEntry(victim));
|
||||
|
||||
if (player)
|
||||
{
|
||||
handler->PSendSysMessage(
|
||||
"ownerCandidate='%s' distance=%.2f canAssistBot=%s canAttackBot=%s",
|
||||
player->GetName().c_str(),
|
||||
bot->GetDistance(player),
|
||||
BoolText(player->IsValidAssistTarget(bot)),
|
||||
BoolText(player->IsValidAttackTarget(bot)));
|
||||
}
|
||||
|
||||
handler->SendSysMessage(
|
||||
"role-specific AI state/cooldowns can be added here as tank/healer roles are implemented.");
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
class VianoBotCommandScript : public CommandScript
|
||||
{
|
||||
public:
|
||||
VianoBotCommandScript() : CommandScript("VianoBotCommandScript") { }
|
||||
|
||||
std::vector<ChatCommand> GetCommands() const override
|
||||
{
|
||||
static std::vector<ChatCommand> botCommands =
|
||||
{
|
||||
{ "info", rbac::RBAC_PERM_COMMAND_GM, false, &HandleInfo, "" },
|
||||
{ "relations", rbac::RBAC_PERM_COMMAND_GM, false, &HandleRelations, "" },
|
||||
{ "combat", rbac::RBAC_PERM_COMMAND_GM, false, &HandleCombat, "" },
|
||||
{ "owner", rbac::RBAC_PERM_COMMAND_GM, false, &HandleOwner, "" },
|
||||
{ "role", rbac::RBAC_PERM_COMMAND_GM, false, &HandleRole, "" },
|
||||
};
|
||||
|
||||
static std::vector<ChatCommand> vianoCommands =
|
||||
{
|
||||
{ "bot", rbac::RBAC_PERM_COMMAND_GM, false, nullptr, "", botCommands },
|
||||
};
|
||||
|
||||
static std::vector<ChatCommand> commandTable =
|
||||
{
|
||||
{ "viano", rbac::RBAC_PERM_COMMAND_GM, false, nullptr, "", vianoCommands },
|
||||
};
|
||||
|
||||
return commandTable;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
void AddSC_viano_bot_commands()
|
||||
{
|
||||
new VianoBotCommands::VianoBotCommandScript();
|
||||
}
|
||||
-457
@@ -1,457 +0,0 @@
|
||||
/*
|
||||
* Viano bot GM diagnostics.
|
||||
*
|
||||
* Read-only commands:
|
||||
* .viano bot info
|
||||
* .viano bot relations
|
||||
* .viano bot combat
|
||||
* .viano bot owner
|
||||
* .viano bot role
|
||||
*/
|
||||
|
||||
#include "ScriptMgr.h"
|
||||
#include "Chat.h"
|
||||
#include "Creature.h"
|
||||
#include "ObjectAccessor.h"
|
||||
#include "ObjectMgr.h"
|
||||
#include "Player.h"
|
||||
#include "RBAC.h"
|
||||
#include "TemporarySummon.h"
|
||||
#include "Unit.h"
|
||||
|
||||
#include <string>
|
||||
|
||||
namespace VianoBotCommands
|
||||
{
|
||||
static char const* BoolText(bool value)
|
||||
{
|
||||
return value ? "yes" : "no";
|
||||
}
|
||||
|
||||
static std::string GuidText(ObjectGuid const& guid)
|
||||
{
|
||||
return guid ? guid.ToString() : std::string("<empty>");
|
||||
}
|
||||
|
||||
static char const* UnitName(Unit const* unit)
|
||||
{
|
||||
return unit ? unit->GetName().c_str() : "<none>";
|
||||
}
|
||||
|
||||
static uint32 UnitEntry(Unit const* unit)
|
||||
{
|
||||
if (Creature const* creature = unit ? unit->ToCreature() : nullptr)
|
||||
return creature->GetEntry();
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static std::string GetScriptName(Creature const* creature)
|
||||
{
|
||||
if (!creature)
|
||||
return {};
|
||||
|
||||
if (CreatureTemplate const* creatureTemplate = creature->GetCreatureTemplate())
|
||||
return sObjectMgr->GetScriptName(creatureTemplate->ScriptID);
|
||||
|
||||
return {};
|
||||
}
|
||||
|
||||
static bool IsVianoBot(Creature const* creature)
|
||||
{
|
||||
if (!creature)
|
||||
return false;
|
||||
|
||||
std::string const scriptName = GetScriptName(creature);
|
||||
|
||||
return scriptName.rfind("npc_horde_adventurer_", 0) == 0
|
||||
|| scriptName.rfind("npc_alliance_adventurer_", 0) == 0;
|
||||
}
|
||||
|
||||
static Creature* GetSelectedVianoBot(ChatHandler* handler)
|
||||
{
|
||||
if (!handler || !handler->GetSession())
|
||||
return nullptr;
|
||||
|
||||
Player* player = handler->GetSession()->GetPlayer();
|
||||
if (!player)
|
||||
return nullptr;
|
||||
|
||||
Unit* selected = player->GetSelectedUnit();
|
||||
Creature* creature = selected ? selected->ToCreature() : nullptr;
|
||||
|
||||
if (!creature)
|
||||
{
|
||||
handler->SendSysMessage("Select a Viano bot creature first.");
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
if (!IsVianoBot(creature))
|
||||
{
|
||||
handler->PSendSysMessage(
|
||||
"Selected creature is not a recognized Viano bot. entry=%u script='%s'",
|
||||
creature->GetEntry(), GetScriptName(creature).c_str());
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
return creature;
|
||||
}
|
||||
|
||||
static Player* GetCommandPlayer(ChatHandler* handler)
|
||||
{
|
||||
return handler && handler->GetSession() ? handler->GetSession()->GetPlayer() : nullptr;
|
||||
}
|
||||
|
||||
static Unit* GetResolvedController(Creature* bot)
|
||||
{
|
||||
return bot ? bot->GetCharmerOrOwnerOrCreatorOrSelf() : nullptr;
|
||||
}
|
||||
|
||||
static char const* DetectRole(Creature const* bot)
|
||||
{
|
||||
std::string const scriptName = GetScriptName(bot);
|
||||
|
||||
if (scriptName.find("_melee") != std::string::npos)
|
||||
return "damage-melee";
|
||||
if (scriptName.find("_tank") != std::string::npos)
|
||||
return "tank";
|
||||
if (scriptName.find("_healer") != std::string::npos)
|
||||
return "healer";
|
||||
if (scriptName.find("_ranged") != std::string::npos)
|
||||
return "damage-ranged";
|
||||
|
||||
return "unknown";
|
||||
}
|
||||
|
||||
static void PrintHeader(ChatHandler* handler, Creature* bot, char const* section)
|
||||
{
|
||||
handler->PSendSysMessage(
|
||||
"[VianoBot:%s] name='%s' entry=%u guid=%s script='%s'",
|
||||
section,
|
||||
bot->GetName().c_str(),
|
||||
bot->GetEntry(),
|
||||
GuidText(bot->GetGUID()).c_str(),
|
||||
GetScriptName(bot).c_str());
|
||||
}
|
||||
|
||||
static bool HandleInfo(ChatHandler* handler, char const* /*args*/)
|
||||
{
|
||||
Creature* bot = GetSelectedVianoBot(handler);
|
||||
if (!bot)
|
||||
return true;
|
||||
|
||||
Player* gm = GetCommandPlayer(handler);
|
||||
Unit* victim = bot->GetVictim();
|
||||
|
||||
PrintHeader(handler, bot, "info");
|
||||
|
||||
handler->PSendSysMessage(
|
||||
"level=%u alive=%s health=%u/%u faction=%u react=%u combat=%s attackers=%u",
|
||||
uint32(bot->getLevel()),
|
||||
BoolText(bot->IsAlive()),
|
||||
bot->GetHealth(),
|
||||
bot->GetMaxHealth(),
|
||||
bot->GetFaction(),
|
||||
uint32(bot->GetReactState()),
|
||||
BoolText(bot->IsInCombat()),
|
||||
uint32(bot->getAttackers().size()));
|
||||
|
||||
handler->PSendSysMessage(
|
||||
"unitFlags=0x%08X immuneToPC=%s immuneToNPC=%s selectable=%s",
|
||||
bot->GetUInt32Value(UNIT_FIELD_FLAGS),
|
||||
BoolText(bot->IsImmuneToPC()),
|
||||
BoolText(bot->IsImmuneToNPC()),
|
||||
BoolText(!bot->HasFlag(UNIT_FIELD_FLAGS, UNIT_FLAG_NOT_SELECTABLE)));
|
||||
|
||||
handler->PSendSysMessage(
|
||||
"victim='%s' entry=%u victimGuid=%s",
|
||||
UnitName(victim),
|
||||
UnitEntry(victim),
|
||||
victim ? GuidText(victim->GetGUID()).c_str() : "<empty>");
|
||||
|
||||
if (gm)
|
||||
handler->PSendSysMessage(
|
||||
"distanceToGM=%.2f gmAssistTarget=%s gmAttackTarget=%s",
|
||||
bot->GetDistance(gm),
|
||||
BoolText(gm->IsValidAssistTarget(bot)),
|
||||
BoolText(gm->IsValidAttackTarget(bot)));
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool HandleRelations(ChatHandler* handler, char const* /*args*/)
|
||||
{
|
||||
Creature* bot = GetSelectedVianoBot(handler);
|
||||
if (!bot)
|
||||
return true;
|
||||
|
||||
Player* player = GetCommandPlayer(handler);
|
||||
if (!player)
|
||||
return true;
|
||||
|
||||
PrintHeader(handler, bot, "relations");
|
||||
|
||||
handler->PSendSysMessage(
|
||||
"player='%s' playerGuid=%s botFaction=%u playerFaction=%u",
|
||||
player->GetName().c_str(),
|
||||
GuidText(player->GetGUID()).c_str(),
|
||||
bot->GetFaction(),
|
||||
player->GetFaction());
|
||||
|
||||
handler->PSendSysMessage(
|
||||
"player->bot: party=%s raid=%s assist=%s attack=%s reaction=%d",
|
||||
BoolText(player->IsInPartyWith(bot)),
|
||||
BoolText(player->IsInRaidWith(bot)),
|
||||
BoolText(player->IsValidAssistTarget(bot)),
|
||||
BoolText(player->IsValidAttackTarget(bot)),
|
||||
int32(player->GetReactionTo(bot)));
|
||||
|
||||
handler->PSendSysMessage(
|
||||
"bot->player: party=%s raid=%s assist=%s attack=%s reaction=%d",
|
||||
BoolText(bot->IsInPartyWith(player)),
|
||||
BoolText(bot->IsInRaidWith(player)),
|
||||
BoolText(bot->IsValidAssistTarget(player)),
|
||||
BoolText(bot->IsValidAttackTarget(player)),
|
||||
int32(bot->GetReactionTo(player)));
|
||||
|
||||
handler->PSendSysMessage(
|
||||
"bot immuneToPC=%s immuneToNPC=%s unitFlags=0x%08X",
|
||||
BoolText(bot->IsImmuneToPC()),
|
||||
BoolText(bot->IsImmuneToNPC()),
|
||||
bot->GetUInt32Value(UNIT_FIELD_FLAGS));
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool HandleCombat(ChatHandler* handler, char const* /*args*/)
|
||||
{
|
||||
Creature* bot = GetSelectedVianoBot(handler);
|
||||
if (!bot)
|
||||
return true;
|
||||
|
||||
Player* player = GetCommandPlayer(handler);
|
||||
Unit* victim = bot->GetVictim();
|
||||
|
||||
PrintHeader(handler, bot, "combat");
|
||||
|
||||
handler->PSendSysMessage(
|
||||
"botCombat=%s victim='%s' entry=%u attackers=%u casting=%s",
|
||||
BoolText(bot->IsInCombat()),
|
||||
UnitName(victim),
|
||||
UnitEntry(victim),
|
||||
uint32(bot->getAttackers().size()),
|
||||
BoolText(bot->HasUnitState(UNIT_STATE_CASTING)));
|
||||
|
||||
uint32 index = 0;
|
||||
for (Unit* attacker : bot->getAttackers())
|
||||
{
|
||||
if (!attacker)
|
||||
continue;
|
||||
|
||||
handler->PSendSysMessage(
|
||||
"attacker[%u]='%s' entry=%u guid=%s alive=%s victim='%s'(%u)",
|
||||
index++,
|
||||
attacker->GetName().c_str(),
|
||||
UnitEntry(attacker),
|
||||
GuidText(attacker->GetGUID()).c_str(),
|
||||
BoolText(attacker->IsAlive()),
|
||||
UnitName(attacker->GetVictim()),
|
||||
UnitEntry(attacker->GetVictim()));
|
||||
}
|
||||
|
||||
if (index == 0)
|
||||
handler->SendSysMessage("attackers=<none>");
|
||||
|
||||
if (player)
|
||||
{
|
||||
Unit* playerVictim = player->GetVictim();
|
||||
Unit* selected = player->GetSelectedUnit();
|
||||
|
||||
handler->PSendSysMessage(
|
||||
"playerCombat=%s playerVictim='%s'(%u) selected='%s'(%u)",
|
||||
BoolText(player->IsInCombat()),
|
||||
UnitName(playerVictim),
|
||||
UnitEntry(playerVictim),
|
||||
UnitName(selected),
|
||||
UnitEntry(selected));
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool HandleOwner(ChatHandler* handler, char const* /*args*/)
|
||||
{
|
||||
Creature* bot = GetSelectedVianoBot(handler);
|
||||
if (!bot)
|
||||
return true;
|
||||
|
||||
Unit* resolved = GetResolvedController(bot);
|
||||
Unit* owner = bot->GetOwner();
|
||||
Unit* creator = bot->GetCreatorGUID()
|
||||
? ObjectAccessor::GetUnit(*bot, bot->GetCreatorGUID())
|
||||
: nullptr;
|
||||
Unit* charmer = bot->GetCharmer();
|
||||
|
||||
PrintHeader(handler, bot, "owner");
|
||||
|
||||
handler->PSendSysMessage(
|
||||
"OwnerGUID=%s CreatorGUID=%s CharmerGUID=%s",
|
||||
GuidText(bot->GetOwnerGUID()).c_str(),
|
||||
GuidText(bot->GetCreatorGUID()).c_str(),
|
||||
GuidText(bot->GetCharmerGUID()).c_str());
|
||||
|
||||
handler->PSendSysMessage(
|
||||
"owner='%s'(%u) creator='%s'(%u) charmer='%s'(%u)",
|
||||
UnitName(owner), UnitEntry(owner),
|
||||
UnitName(creator), UnitEntry(creator),
|
||||
UnitName(charmer), UnitEntry(charmer));
|
||||
|
||||
handler->PSendSysMessage(
|
||||
"resolvedController='%s' entry=%u guid=%s typeId=%u",
|
||||
UnitName(resolved),
|
||||
UnitEntry(resolved),
|
||||
resolved ? GuidText(resolved->GetGUID()).c_str() : "<empty>",
|
||||
resolved ? uint32(resolved->GetTypeId()) : 0u);
|
||||
|
||||
if (TempSummon* summon = bot->ToTempSummon())
|
||||
{
|
||||
Unit* summoner = summon->GetSummoner();
|
||||
handler->PSendSysMessage(
|
||||
"tempSummon=yes summoner='%s' entry=%u guid=%s",
|
||||
UnitName(summoner),
|
||||
UnitEntry(summoner),
|
||||
summoner ? GuidText(summoner->GetGUID()).c_str() : "<empty>");
|
||||
}
|
||||
else
|
||||
handler->SendSysMessage("tempSummon=no");
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool HandleRole(ChatHandler* handler, char const* /*args*/)
|
||||
{
|
||||
Creature* bot = GetSelectedVianoBot(handler);
|
||||
if (!bot)
|
||||
return true;
|
||||
|
||||
Player* player = GetCommandPlayer(handler);
|
||||
Unit* victim = bot->GetVictim();
|
||||
|
||||
PrintHeader(handler, bot, "role");
|
||||
|
||||
handler->PSendSysMessage(
|
||||
"role=%s react=%u level=%u combat=%s victim='%s'(%u)",
|
||||
DetectRole(bot),
|
||||
uint32(bot->GetReactState()),
|
||||
uint32(bot->getLevel()),
|
||||
BoolText(bot->IsInCombat()),
|
||||
UnitName(victim),
|
||||
UnitEntry(victim));
|
||||
|
||||
if (player)
|
||||
{
|
||||
handler->PSendSysMessage(
|
||||
"ownerCandidate='%s' distance=%.2f canAssistBot=%s canAttackBot=%s",
|
||||
player->GetName().c_str(),
|
||||
bot->GetDistance(player),
|
||||
BoolText(player->IsValidAssistTarget(bot)),
|
||||
BoolText(player->IsValidAttackTarget(bot)));
|
||||
}
|
||||
|
||||
handler->SendSysMessage(
|
||||
"role-specific AI state/cooldowns can be added here as tank/healer roles are implemented.");
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
// VIANO_BOT_SUMMON_COMMAND_V1
|
||||
static bool HandleSummon(ChatHandler* handler, char const* args)
|
||||
{
|
||||
Player* player = GetCommandPlayer(handler);
|
||||
if (!player)
|
||||
return true;
|
||||
|
||||
std::string role = args ? args : "";
|
||||
std::string::size_type first = role.find_first_not_of(" \t");
|
||||
std::string::size_type last = role.find_last_not_of(" \t");
|
||||
|
||||
if (first == std::string::npos)
|
||||
{
|
||||
handler->SendSysMessage("Usage: .viano bot summon melee|tank");
|
||||
return true;
|
||||
}
|
||||
|
||||
role = role.substr(first, last - first + 1);
|
||||
|
||||
uint32 entry = 0;
|
||||
if (role == "melee")
|
||||
entry = 900001;
|
||||
else if (role == "tank")
|
||||
entry = 900002;
|
||||
else
|
||||
{
|
||||
handler->SendSysMessage("Unknown role. Usage: .viano bot summon melee|tank");
|
||||
return true;
|
||||
}
|
||||
|
||||
Creature* summon = player->SummonCreature(
|
||||
entry,
|
||||
player->GetPositionX() + 1.5f,
|
||||
player->GetPositionY(),
|
||||
player->GetPositionZ(),
|
||||
player->GetOrientation(),
|
||||
TEMPSUMMON_MANUAL_DESPAWN,
|
||||
0);
|
||||
|
||||
if (!summon)
|
||||
{
|
||||
handler->PSendSysMessage("Failed to summon Viano bot entry=%u.", entry);
|
||||
return true;
|
||||
}
|
||||
|
||||
handler->PSendSysMessage(
|
||||
"Summoned Viano bot role=%s entry=%u guid=%s",
|
||||
role.c_str(),
|
||||
entry,
|
||||
GuidText(summon->GetGUID()).c_str());
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
class VianoBotCommandScript : public CommandScript
|
||||
{
|
||||
public:
|
||||
VianoBotCommandScript() : CommandScript("VianoBotCommandScript") { }
|
||||
|
||||
std::vector<ChatCommand> GetCommands() const override
|
||||
{
|
||||
static std::vector<ChatCommand> botCommands =
|
||||
{
|
||||
{ "info", rbac::RBAC_PERM_COMMAND_GM, false, &HandleInfo, "" },
|
||||
{ "relations", rbac::RBAC_PERM_COMMAND_GM, false, &HandleRelations, "" },
|
||||
{ "combat", rbac::RBAC_PERM_COMMAND_GM, false, &HandleCombat, "" },
|
||||
{ "owner", rbac::RBAC_PERM_COMMAND_GM, false, &HandleOwner, "" },
|
||||
{ "role", rbac::RBAC_PERM_COMMAND_GM, false, &HandleRole, "" },
|
||||
{ "summon", rbac::RBAC_PERM_COMMAND_GM, false, &HandleSummon, "" },
|
||||
};
|
||||
|
||||
static std::vector<ChatCommand> vianoCommands =
|
||||
{
|
||||
{ "bot", rbac::RBAC_PERM_COMMAND_GM, false, nullptr, "", botCommands },
|
||||
};
|
||||
|
||||
static std::vector<ChatCommand> commandTable =
|
||||
{
|
||||
{ "viano", rbac::RBAC_PERM_COMMAND_GM, false, nullptr, "", vianoCommands },
|
||||
};
|
||||
|
||||
return commandTable;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
void AddSC_viano_bot_commands()
|
||||
{
|
||||
new VianoBotCommands::VianoBotCommandScript();
|
||||
}
|
||||
@@ -1,401 +0,0 @@
|
||||
/*
|
||||
* Viano bot GM diagnostics.
|
||||
*
|
||||
* Read-only commands:
|
||||
* .viano bot info
|
||||
* .viano bot relations
|
||||
* .viano bot combat
|
||||
* .viano bot owner
|
||||
* .viano bot role
|
||||
*/
|
||||
|
||||
#include "ScriptMgr.h"
|
||||
#include "Chat.h"
|
||||
#include "Creature.h"
|
||||
#include "ObjectAccessor.h"
|
||||
#include "ObjectMgr.h"
|
||||
#include "Player.h"
|
||||
#include "RBAC.h"
|
||||
#include "TemporarySummon.h"
|
||||
#include "Unit.h"
|
||||
|
||||
#include <string>
|
||||
|
||||
namespace VianoBotCommands
|
||||
{
|
||||
static char const* BoolText(bool value)
|
||||
{
|
||||
return value ? "yes" : "no";
|
||||
}
|
||||
|
||||
static std::string GuidText(ObjectGuid const& guid)
|
||||
{
|
||||
return guid ? guid.ToString() : std::string("<empty>");
|
||||
}
|
||||
|
||||
static char const* UnitName(Unit const* unit)
|
||||
{
|
||||
return unit ? unit->GetName().c_str() : "<none>";
|
||||
}
|
||||
|
||||
static uint32 UnitEntry(Unit const* unit)
|
||||
{
|
||||
if (Creature const* creature = unit ? unit->ToCreature() : nullptr)
|
||||
return creature->GetEntry();
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static std::string GetScriptName(Creature const* creature)
|
||||
{
|
||||
if (!creature)
|
||||
return {};
|
||||
|
||||
if (CreatureTemplate const* creatureTemplate = creature->GetCreatureTemplate())
|
||||
return sObjectMgr->GetScriptName(creatureTemplate->ScriptID);
|
||||
|
||||
return {};
|
||||
}
|
||||
|
||||
static bool IsVianoBot(Creature const* creature)
|
||||
{
|
||||
if (!creature)
|
||||
return false;
|
||||
|
||||
std::string const scriptName = GetScriptName(creature);
|
||||
|
||||
return scriptName.rfind("npc_horde_adventurer_", 0) == 0
|
||||
|| scriptName.rfind("npc_alliance_adventurer_", 0) == 0;
|
||||
}
|
||||
|
||||
static Creature* GetSelectedVianoBot(ChatHandler* handler)
|
||||
{
|
||||
if (!handler || !handler->GetSession())
|
||||
return nullptr;
|
||||
|
||||
Player* player = handler->GetSession()->GetPlayer();
|
||||
if (!player)
|
||||
return nullptr;
|
||||
|
||||
Unit* selected = player->GetSelectedUnit();
|
||||
Creature* creature = selected ? selected->ToCreature() : nullptr;
|
||||
|
||||
if (!creature)
|
||||
{
|
||||
handler->SendSysMessage("Select a Viano bot creature first.");
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
if (!IsVianoBot(creature))
|
||||
{
|
||||
handler->PSendSysMessage(
|
||||
"Selected creature is not a recognized Viano bot. entry=%u script='%s'",
|
||||
creature->GetEntry(), GetScriptName(creature).c_str());
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
return creature;
|
||||
}
|
||||
|
||||
static Player* GetCommandPlayer(ChatHandler* handler)
|
||||
{
|
||||
return handler && handler->GetSession() ? handler->GetSession()->GetPlayer() : nullptr;
|
||||
}
|
||||
|
||||
static Unit* GetResolvedController(Creature* bot)
|
||||
{
|
||||
return bot ? bot->GetCharmerOrOwnerOrCreatorOrSelf() : nullptr;
|
||||
}
|
||||
|
||||
static char const* DetectRole(Creature const* bot)
|
||||
{
|
||||
std::string const scriptName = GetScriptName(bot);
|
||||
|
||||
if (scriptName.find("_melee") != std::string::npos)
|
||||
return "damage-melee";
|
||||
if (scriptName.find("_tank") != std::string::npos)
|
||||
return "tank";
|
||||
if (scriptName.find("_healer") != std::string::npos)
|
||||
return "healer";
|
||||
if (scriptName.find("_ranged") != std::string::npos)
|
||||
return "damage-ranged";
|
||||
|
||||
return "unknown";
|
||||
}
|
||||
|
||||
static void PrintHeader(ChatHandler* handler, Creature* bot, char const* section)
|
||||
{
|
||||
handler->PSendSysMessage(
|
||||
"[VianoBot:%s] name='%s' entry=%u guid=%s script='%s'",
|
||||
section,
|
||||
bot->GetName().c_str(),
|
||||
bot->GetEntry(),
|
||||
GuidText(bot->GetGUID()).c_str(),
|
||||
GetScriptName(bot).c_str());
|
||||
}
|
||||
|
||||
static bool HandleInfo(ChatHandler* handler, char const* /*args*/)
|
||||
{
|
||||
Creature* bot = GetSelectedVianoBot(handler);
|
||||
if (!bot)
|
||||
return true;
|
||||
|
||||
Player* gm = GetCommandPlayer(handler);
|
||||
Unit* victim = bot->GetVictim();
|
||||
|
||||
PrintHeader(handler, bot, "info");
|
||||
|
||||
handler->PSendSysMessage(
|
||||
"level=%u alive=%s health=%u/%u faction=%u react=%u combat=%s attackers=%u",
|
||||
uint32(bot->getLevel()),
|
||||
BoolText(bot->IsAlive()),
|
||||
bot->GetHealth(),
|
||||
bot->GetMaxHealth(),
|
||||
bot->GetFaction(),
|
||||
uint32(bot->GetReactState()),
|
||||
BoolText(bot->IsInCombat()),
|
||||
uint32(bot->getAttackers().size()));
|
||||
|
||||
handler->PSendSysMessage(
|
||||
"unitFlags=0x%08X immuneToPC=%s immuneToNPC=%s selectable=%s",
|
||||
bot->GetUInt32Value(UNIT_FIELD_FLAGS),
|
||||
BoolText(bot->IsImmuneToPC()),
|
||||
BoolText(bot->IsImmuneToNPC()),
|
||||
BoolText(!bot->HasFlag(UNIT_FIELD_FLAGS, UNIT_FLAG_NOT_SELECTABLE)));
|
||||
|
||||
handler->PSendSysMessage(
|
||||
"victim='%s' entry=%u victimGuid=%s",
|
||||
UnitName(victim),
|
||||
UnitEntry(victim),
|
||||
victim ? GuidText(victim->GetGUID()).c_str() : "<empty>");
|
||||
|
||||
if (gm)
|
||||
handler->PSendSysMessage(
|
||||
"distanceToGM=%.2f gmAssistTarget=%s gmAttackTarget=%s",
|
||||
bot->GetDistance(gm),
|
||||
BoolText(gm->IsValidAssistTarget(bot)),
|
||||
BoolText(gm->IsValidAttackTarget(bot)));
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool HandleRelations(ChatHandler* handler, char const* /*args*/)
|
||||
{
|
||||
Creature* bot = GetSelectedVianoBot(handler);
|
||||
if (!bot)
|
||||
return true;
|
||||
|
||||
Player* player = GetCommandPlayer(handler);
|
||||
if (!player)
|
||||
return true;
|
||||
|
||||
PrintHeader(handler, bot, "relations");
|
||||
|
||||
handler->PSendSysMessage(
|
||||
"player='%s' playerGuid=%s botFaction=%u playerFaction=%u",
|
||||
player->GetName().c_str(),
|
||||
GuidText(player->GetGUID()).c_str(),
|
||||
bot->GetFaction(),
|
||||
player->GetFaction());
|
||||
|
||||
handler->PSendSysMessage(
|
||||
"player->bot: party=%s raid=%s assist=%s attack=%s reaction=%d",
|
||||
BoolText(player->IsInPartyWith(bot)),
|
||||
BoolText(player->IsInRaidWith(bot)),
|
||||
BoolText(player->IsValidAssistTarget(bot)),
|
||||
BoolText(player->IsValidAttackTarget(bot)),
|
||||
int32(player->GetReactionTo(bot)));
|
||||
|
||||
handler->PSendSysMessage(
|
||||
"bot->player: party=%s raid=%s assist=%s attack=%s reaction=%d",
|
||||
BoolText(bot->IsInPartyWith(player)),
|
||||
BoolText(bot->IsInRaidWith(player)),
|
||||
BoolText(bot->IsValidAssistTarget(player)),
|
||||
BoolText(bot->IsValidAttackTarget(player)),
|
||||
int32(bot->GetReactionTo(player)));
|
||||
|
||||
handler->PSendSysMessage(
|
||||
"bot immuneToPC=%s immuneToNPC=%s unitFlags=0x%08X",
|
||||
BoolText(bot->IsImmuneToPC()),
|
||||
BoolText(bot->IsImmuneToNPC()),
|
||||
bot->GetUInt32Value(UNIT_FIELD_FLAGS));
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool HandleCombat(ChatHandler* handler, char const* /*args*/)
|
||||
{
|
||||
Creature* bot = GetSelectedVianoBot(handler);
|
||||
if (!bot)
|
||||
return true;
|
||||
|
||||
Player* player = GetCommandPlayer(handler);
|
||||
Unit* victim = bot->GetVictim();
|
||||
|
||||
PrintHeader(handler, bot, "combat");
|
||||
|
||||
handler->PSendSysMessage(
|
||||
"botCombat=%s victim='%s' entry=%u attackers=%u casting=%s",
|
||||
BoolText(bot->IsInCombat()),
|
||||
UnitName(victim),
|
||||
UnitEntry(victim),
|
||||
uint32(bot->getAttackers().size()),
|
||||
BoolText(bot->HasUnitState(UNIT_STATE_CASTING)));
|
||||
|
||||
uint32 index = 0;
|
||||
for (Unit* attacker : bot->getAttackers())
|
||||
{
|
||||
if (!attacker)
|
||||
continue;
|
||||
|
||||
handler->PSendSysMessage(
|
||||
"attacker[%u]='%s' entry=%u guid=%s alive=%s victim='%s'(%u)",
|
||||
index++,
|
||||
attacker->GetName().c_str(),
|
||||
UnitEntry(attacker),
|
||||
GuidText(attacker->GetGUID()).c_str(),
|
||||
BoolText(attacker->IsAlive()),
|
||||
UnitName(attacker->GetVictim()),
|
||||
UnitEntry(attacker->GetVictim()));
|
||||
}
|
||||
|
||||
if (index == 0)
|
||||
handler->SendSysMessage("attackers=<none>");
|
||||
|
||||
if (player)
|
||||
{
|
||||
Unit* playerVictim = player->GetVictim();
|
||||
Unit* selected = player->GetSelectedUnit();
|
||||
|
||||
handler->PSendSysMessage(
|
||||
"playerCombat=%s playerVictim='%s'(%u) selected='%s'(%u)",
|
||||
BoolText(player->IsInCombat()),
|
||||
UnitName(playerVictim),
|
||||
UnitEntry(playerVictim),
|
||||
UnitName(selected),
|
||||
UnitEntry(selected));
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool HandleOwner(ChatHandler* handler, char const* /*args*/)
|
||||
{
|
||||
Creature* bot = GetSelectedVianoBot(handler);
|
||||
if (!bot)
|
||||
return true;
|
||||
|
||||
Unit* resolved = GetResolvedController(bot);
|
||||
Unit* owner = bot->GetOwner();
|
||||
Unit* creator = bot->GetCreatorGUID()
|
||||
? ObjectAccessor::GetUnit(*bot, bot->GetCreatorGUID())
|
||||
: nullptr;
|
||||
Unit* charmer = bot->GetCharmer();
|
||||
|
||||
PrintHeader(handler, bot, "owner");
|
||||
|
||||
handler->PSendSysMessage(
|
||||
"OwnerGUID=%s CreatorGUID=%s CharmerGUID=%s",
|
||||
GuidText(bot->GetOwnerGUID()).c_str(),
|
||||
GuidText(bot->GetCreatorGUID()).c_str(),
|
||||
GuidText(bot->GetCharmerGUID()).c_str());
|
||||
|
||||
handler->PSendSysMessage(
|
||||
"owner='%s'(%u) creator='%s'(%u) charmer='%s'(%u)",
|
||||
UnitName(owner), UnitEntry(owner),
|
||||
UnitName(creator), UnitEntry(creator),
|
||||
UnitName(charmer), UnitEntry(charmer));
|
||||
|
||||
handler->PSendSysMessage(
|
||||
"resolvedController='%s' entry=%u guid=%s typeId=%u",
|
||||
UnitName(resolved),
|
||||
UnitEntry(resolved),
|
||||
resolved ? GuidText(resolved->GetGUID()).c_str() : "<empty>",
|
||||
resolved ? uint32(resolved->GetTypeId()) : 0u);
|
||||
|
||||
if (TempSummon* summon = bot->ToTempSummon())
|
||||
{
|
||||
Unit* summoner = summon->GetSummoner();
|
||||
handler->PSendSysMessage(
|
||||
"tempSummon=yes summoner='%s' entry=%u guid=%s",
|
||||
UnitName(summoner),
|
||||
UnitEntry(summoner),
|
||||
summoner ? GuidText(summoner->GetGUID()).c_str() : "<empty>");
|
||||
}
|
||||
else
|
||||
handler->SendSysMessage("tempSummon=no");
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool HandleRole(ChatHandler* handler, char const* /*args*/)
|
||||
{
|
||||
Creature* bot = GetSelectedVianoBot(handler);
|
||||
if (!bot)
|
||||
return true;
|
||||
|
||||
Player* player = GetCommandPlayer(handler);
|
||||
Unit* victim = bot->GetVictim();
|
||||
|
||||
PrintHeader(handler, bot, "role");
|
||||
|
||||
handler->PSendSysMessage(
|
||||
"role=%s react=%u level=%u combat=%s victim='%s'(%u)",
|
||||
DetectRole(bot),
|
||||
uint32(bot->GetReactState()),
|
||||
uint32(bot->getLevel()),
|
||||
BoolText(bot->IsInCombat()),
|
||||
UnitName(victim),
|
||||
UnitEntry(victim));
|
||||
|
||||
if (player)
|
||||
{
|
||||
handler->PSendSysMessage(
|
||||
"ownerCandidate='%s' distance=%.2f canAssistBot=%s canAttackBot=%s",
|
||||
player->GetName().c_str(),
|
||||
bot->GetDistance(player),
|
||||
BoolText(player->IsValidAssistTarget(bot)),
|
||||
BoolText(player->IsValidAttackTarget(bot)));
|
||||
}
|
||||
|
||||
handler->SendSysMessage(
|
||||
"role-specific AI state/cooldowns can be added here as tank/healer roles are implemented.");
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
class VianoBotCommandScript : public CommandScript
|
||||
{
|
||||
public:
|
||||
VianoBotCommandScript() : CommandScript("VianoBotCommandScript") { }
|
||||
|
||||
std::vector<ChatCommand> GetCommands() const override
|
||||
{
|
||||
static std::vector<ChatCommand> botCommands =
|
||||
{
|
||||
{ "info", rbac::RBAC_PERM_COMMAND_GM, false, &HandleInfo, "" },
|
||||
{ "relations", rbac::RBAC_PERM_COMMAND_GM, false, &HandleRelations, "" },
|
||||
{ "combat", rbac::RBAC_PERM_COMMAND_GM, false, &HandleCombat, "" },
|
||||
{ "owner", rbac::RBAC_PERM_COMMAND_GM, false, &HandleOwner, "" },
|
||||
{ "role", rbac::RBAC_PERM_COMMAND_GM, false, &HandleRole, "" },
|
||||
};
|
||||
|
||||
static std::vector<ChatCommand> vianoCommands =
|
||||
{
|
||||
{ "bot", rbac::RBAC_PERM_COMMAND_GM, false, nullptr, "", botCommands },
|
||||
};
|
||||
|
||||
static std::vector<ChatCommand> commandTable =
|
||||
{
|
||||
{ "viano", rbac::RBAC_PERM_COMMAND_GM, false, nullptr, "", vianoCommands },
|
||||
};
|
||||
|
||||
return commandTable;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
void AddSC_viano_bot_commands()
|
||||
{
|
||||
new VianoBotCommands::VianoBotCommandScript();
|
||||
}
|
||||
@@ -426,6 +426,14 @@ struct npc_greymanes_horse : public VehicleAI
|
||||
{
|
||||
npc_greymanes_horse(Creature* creature) : VehicleAI(creature), _currentPath(PATH_KRENNAN_TREE) { }
|
||||
|
||||
void DamageTaken(Unit* /*attacker*/, uint32& damage) override
|
||||
{
|
||||
if (me->GetHealth() <= 1)
|
||||
damage = 0;
|
||||
else if (damage >= me->GetHealth())
|
||||
damage = me->GetHealth() - 1;
|
||||
}
|
||||
|
||||
void PassengerBoarded(Unit* passenger, int8 /*seatId*/, bool apply) override
|
||||
{
|
||||
if (apply && passenger->GetTypeId() == TYPEID_PLAYER)
|
||||
@@ -540,10 +548,76 @@ enum CrowleysHorse
|
||||
static Position const CrowleysHorseJumpPos = { -1714.762f, 1673.16f, 20.49182f };
|
||||
static Position const CrowleysHorseJumpPos2 = { -1566.71f, 1708.04f, 20.4849f };
|
||||
|
||||
static Position const CrowleysHorsePath1[] =
|
||||
{
|
||||
{ -1686.12f, 1655.64f, 20.6106f },
|
||||
{ -1668.16f, 1639.43f, 20.6106f },
|
||||
{ -1666.63f, 1628.86f, 20.5327f },
|
||||
{ -1670.76f, 1617.43f, 20.6106f },
|
||||
{ -1693.65f, 1592.64f, 20.6106f },
|
||||
{ -1708.02f, 1590.55f, 20.6104f },
|
||||
{ -1719.37f, 1595.65f, 20.6104f },
|
||||
{ -1720.57f, 1610.14f, 20.6074f },
|
||||
{ -1711.90f, 1629.70f, 20.6074f },
|
||||
{ -1698.15f, 1656.48f, 20.6106f },
|
||||
{ -1696.42f, 1682.01f, 20.5262f },
|
||||
{ -1684.68f, 1700.12f, 20.4648f },
|
||||
{ -1658.31f, 1710.31f, 20.5898f },
|
||||
{ -1625.48f, 1712.30f, 21.8051f },
|
||||
{ -1592.09f, 1710.71f, 20.5899f }
|
||||
};
|
||||
|
||||
|
||||
static Position const CrowleysHorsePath2[] =
|
||||
{
|
||||
{ -1545.08f, 1684.23f, 20.6099f },
|
||||
{ -1542.79f, 1656.08f, 20.6099f },
|
||||
{ -1543.0f, 1640.54f, 20.6099f },
|
||||
{ -1520.73f, 1618.1f, 20.6105f },
|
||||
{ -1498.79f, 1621.74f, 20.6105f },
|
||||
{ -1468.36f, 1630.78f, 20.6105f },
|
||||
{ -1441.65f, 1627.67f, 20.6105f },
|
||||
{ -1429.84f, 1616.63f, 20.5898f },
|
||||
{ -1417.99f, 1586.55f, 20.5898f },
|
||||
{ -1429.18f, 1548.78f, 20.8485f },
|
||||
{ -1447.42f, 1532.79f, 20.601f },
|
||||
{ -1463.1f, 1535.03f, 20.6105f },
|
||||
{ -1483.04f, 1544.15f, 20.6105f },
|
||||
{ -1505.16f, 1568.61f, 20.6105f },
|
||||
{ -1531.6f, 1581.87f, 26.5387f },
|
||||
{ -1540.59f, 1574.43f, 29.2067f },
|
||||
};
|
||||
|
||||
static Position const CrowleysHorsePath3[] =
|
||||
{
|
||||
{ -1512.36f, 1570.78f, 24.0671f },
|
||||
{ -1498.71f, 1565.43f, 20.8781f },
|
||||
{ -1487.86f, 1557.16f, 20.85f },
|
||||
{ -1474.18f, 1542.68f, 20.6105f },
|
||||
{ -1457.6f, 1532.64f, 20.601f },
|
||||
{ -1438.01f, 1525.31f, 20.8762f },
|
||||
{ -1424.2f, 1545.7f, 20.4657f },
|
||||
{ -1411.29f, 1556.16f, 20.557f },
|
||||
{ -1414.19f, 1577.65f, 20.5898f },
|
||||
{ -1427.47f, 1617.3f, 20.5898f },
|
||||
{ -1445.54f, 1633.9f, 20.6105f },
|
||||
{ -1470.71f, 1658.21f, 20.6105f },
|
||||
{ -1491.05f, 1658.74f, 20.6105f },
|
||||
{ -1515.33f, 1641.18f, 20.6099f },
|
||||
{ -1526.75f, 1630.37f, 20.6105f },
|
||||
{ -1528.42f, 1610.71f, 20.6105f },
|
||||
{ -1532.39f, 1585.36f, 26.538f },
|
||||
};
|
||||
|
||||
struct npc_crowleys_horse : public VehicleAI
|
||||
{
|
||||
npc_crowleys_horse(Creature* creature) : VehicleAI(creature), _currentPath(0) { }
|
||||
|
||||
void DamageTaken(Unit* /*attacker*/, uint32& damage) override
|
||||
{
|
||||
damage = 0;
|
||||
}
|
||||
|
||||
void PassengerBoarded(Unit* passenger, int8 /*seatId*/, bool apply) override
|
||||
{
|
||||
if (apply && passenger->GetTypeId() == TYPEID_PLAYER && me->GetEntry() != NPC_CROWLEYS_HORSE_2)
|
||||
@@ -560,11 +634,11 @@ struct npc_crowleys_horse : public VehicleAI
|
||||
|
||||
void MovementInform(uint32 type, uint32 pointId) override
|
||||
{
|
||||
if (type == WAYPOINT_MOTION_TYPE && pointId == 14 && _currentPath == PATH_ID_CROWLEYS_HORSE_1)
|
||||
if (type == EFFECT_MOTION_TYPE && pointId == PATH_ID_CROWLEYS_HORSE_1 && _currentPath == PATH_ID_CROWLEYS_HORSE_1)
|
||||
_events.ScheduleEvent(EVENT_JUMP_OVER_BARRICADES_2, 1s);
|
||||
else if (type == WAYPOINT_MOTION_TYPE && pointId == 15 && _currentPath == PATH_ID_CROWLEYS_HORSE_2)
|
||||
else if (type == EFFECT_MOTION_TYPE && pointId == PATH_ID_CROWLEYS_HORSE_2 && _currentPath == PATH_ID_CROWLEYS_HORSE_2)
|
||||
_events.ScheduleEvent(EVENT_DISMOUNT_PLAYER, 1ms);
|
||||
else if (type == WAYPOINT_MOTION_TYPE && pointId == 16 && _currentPath == PATH_ID_CROWLEYS_HORSE_3)
|
||||
else if (type == EFFECT_MOTION_TYPE && pointId == PATH_ID_CROWLEYS_HORSE_3 && _currentPath == PATH_ID_CROWLEYS_HORSE_3)
|
||||
_events.ScheduleEvent(EVENT_DISMOUNT_PLAYER, 1ms);
|
||||
}
|
||||
|
||||
@@ -582,8 +656,11 @@ struct npc_crowleys_horse : public VehicleAI
|
||||
_events.ScheduleEvent(EVENT_MOVE_PATH_MAIN_1, 2s);
|
||||
break;
|
||||
case EVENT_MOVE_PATH_MAIN_1:
|
||||
me->GetMotionMaster()->MovePath(PATH_ID_CROWLEYS_HORSE_1, false);
|
||||
_currentPath = PATH_ID_CROWLEYS_HORSE_1;
|
||||
me->GetMotionMaster()->MoveSmoothPath(
|
||||
PATH_ID_CROWLEYS_HORSE_1,
|
||||
CrowleysHorsePath1,
|
||||
15);
|
||||
break;
|
||||
case EVENT_JUMP_OVER_BARRICADES_2:
|
||||
me->GetMotionMaster()->MoveJump(CrowleysHorseJumpPos2, 16.0f, 18.56182f);
|
||||
@@ -591,7 +668,10 @@ struct npc_crowleys_horse : public VehicleAI
|
||||
break;
|
||||
case EVENT_MOVE_PATH_MAIN_2:
|
||||
_currentPath = PATH_ID_CROWLEYS_HORSE_2;
|
||||
me->GetMotionMaster()->MovePath(PATH_ID_CROWLEYS_HORSE_2, false);
|
||||
me->GetMotionMaster()->MoveSmoothPath(
|
||||
PATH_ID_CROWLEYS_HORSE_2,
|
||||
CrowleysHorsePath2,
|
||||
16);
|
||||
break;
|
||||
case EVENT_DISMOUNT_PLAYER:
|
||||
{
|
||||
@@ -610,7 +690,10 @@ struct npc_crowleys_horse : public VehicleAI
|
||||
case EVENT_MOVE_OFF_PATH:
|
||||
me->SetControlled(false, UNIT_STATE_ROOT);
|
||||
_currentPath = PATH_ID_CROWLEYS_HORSE_3;
|
||||
me->GetMotionMaster()->MovePath(PATH_ID_CROWLEYS_HORSE_3, false);
|
||||
me->GetMotionMaster()->MoveSmoothPath(
|
||||
PATH_ID_CROWLEYS_HORSE_3,
|
||||
CrowleysHorsePath3,
|
||||
17);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
|
||||
+701
@@ -0,0 +1,701 @@
|
||||
/*
|
||||
* This file is part of the TrinityCore 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 "gilneas.h"
|
||||
#include "ScriptMgr.h"
|
||||
#include "CombatAI.h"
|
||||
#include "MotionMaster.h"
|
||||
#include "ObjectAccessor.h"
|
||||
#include "Player.h"
|
||||
#include "PassiveAI.h"
|
||||
#include "PhasingHandler.h"
|
||||
#include "ScriptedCreature.h"
|
||||
#include "SpellInfo.h"
|
||||
#include "SpellScript.h"
|
||||
#include "TemporarySummon.h"
|
||||
#include "Vehicle.h"
|
||||
|
||||
namespace Gilneas::Chapter1
|
||||
{
|
||||
|
||||
/*######
|
||||
## Quest 14098 - Evacuate the Merchant Square
|
||||
######*/
|
||||
|
||||
enum FrightenedCitizen
|
||||
{
|
||||
SAY_FRIGHTENED_CITIZEN_RESCUE = 0,
|
||||
|
||||
NPC_EVACUATION_STALKER_FIRST = 35830,
|
||||
NPC_EVACUATION_STALKER_NEAR = 35010,
|
||||
NPC_EVACUATION_STALKER_FAR = 35011,
|
||||
NPC_RAMPAGING_WORGEN = 35660,
|
||||
|
||||
CREDIT_35830 = 35830,
|
||||
|
||||
POINT_STALKER_FIRST = 1,
|
||||
POINT_STALKER_NEAR = 2,
|
||||
POINT_STALKER_FAR = 3,
|
||||
|
||||
EVENT_TALK_FRIGHTENED = 1,
|
||||
EVENT_MOVE_TO_NEAR_STALKER = 2,
|
||||
EVENT_MOVE_TO_FAR_STALKER = 3,
|
||||
EVENT_DESPAWN = 4
|
||||
};
|
||||
|
||||
struct npc_frightened_citizen : public PassiveAI
|
||||
{
|
||||
npc_frightened_citizen(Creature* creature) : PassiveAI(creature) { }
|
||||
|
||||
void IsSummonedBy(Unit* /*summoner*/) override
|
||||
{
|
||||
if (Creature* stalkerNear = me->FindNearestCreature(NPC_EVACUATION_STALKER_FIRST, 20.0f))
|
||||
me->GetMotionMaster()->MovePoint(POINT_STALKER_FIRST, stalkerNear->GetPosition(), true);
|
||||
}
|
||||
|
||||
void MovementInform(uint32 type, uint32 id) override
|
||||
{
|
||||
if (type != POINT_MOTION_TYPE)
|
||||
return;
|
||||
|
||||
switch (id)
|
||||
{
|
||||
case POINT_STALKER_FIRST:
|
||||
_events.ScheduleEvent(EVENT_TALK_FRIGHTENED, 1s);
|
||||
break;
|
||||
case POINT_STALKER_NEAR:
|
||||
_events.ScheduleEvent(EVENT_MOVE_TO_FAR_STALKER, 1ms);
|
||||
break;
|
||||
case POINT_STALKER_FAR:
|
||||
_events.ScheduleEvent(EVENT_DESPAWN, 1ms);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void UpdateAI(uint32 diff) override
|
||||
{
|
||||
_events.Update(diff);
|
||||
|
||||
while (uint32 eventId = _events.ExecuteEvent())
|
||||
{
|
||||
switch (eventId)
|
||||
{
|
||||
case EVENT_TALK_FRIGHTENED:
|
||||
if (Unit* summoner = me->ToTempSummon()->GetSummoner())
|
||||
{
|
||||
if (Player* player = summoner->ToPlayer())
|
||||
{
|
||||
player->KilledMonsterCredit(CREDIT_35830);
|
||||
Talk(SAY_FRIGHTENED_CITIZEN_RESCUE, summoner);
|
||||
}
|
||||
}
|
||||
_events.ScheduleEvent(EVENT_MOVE_TO_NEAR_STALKER, 2s);
|
||||
break;
|
||||
case EVENT_MOVE_TO_NEAR_STALKER:
|
||||
if (Creature* stalker = me->FindNearestCreature(NPC_EVACUATION_STALKER_FAR, 50.0f))
|
||||
me->GetMotionMaster()->MovePoint(POINT_STALKER_FAR, stalker->GetPosition(), true);
|
||||
else if (Creature* stalker = me->FindNearestCreature(NPC_EVACUATION_STALKER_NEAR, 100.0f))
|
||||
me->GetMotionMaster()->MovePoint(POINT_STALKER_NEAR, stalker->GetPosition(), true);
|
||||
break;
|
||||
case EVENT_MOVE_TO_FAR_STALKER:
|
||||
if (Creature* stalker = me->FindNearestCreature(NPC_EVACUATION_STALKER_FAR, 500.0f))
|
||||
me->GetMotionMaster()->MovePoint(POINT_STALKER_FAR, stalker->GetPosition(), true);
|
||||
break;
|
||||
case EVENT_DESPAWN:
|
||||
me->DespawnOrUnsummon();
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
private:
|
||||
EventMap _events;
|
||||
};
|
||||
|
||||
/*######
|
||||
## Quest 14154 - By the Skin of his Teeth
|
||||
######*/
|
||||
|
||||
enum WorgenRunt
|
||||
{
|
||||
EVENT_JUMP_TO_PRISON = 1,
|
||||
EVENT_AGGRO_PLAYER = 2,
|
||||
EVENT_FORCE_DESPAWN = 3,
|
||||
|
||||
PHASE_ROOF = 0,
|
||||
PHASE_COMBAT = 1,
|
||||
|
||||
WORGEN_ID_ROOF_1 = 0,
|
||||
WORGEN_ID_ROOF_2 = 1,
|
||||
WORGEN_ID_ROOF_3 = 2,
|
||||
WORGEN_ID_ROOF_4 = 3,
|
||||
WORGEN_ID_ROOF_5 = 4,
|
||||
WORGEN_ID_ROOF_6 = 5,
|
||||
WORGEN_ID_ROOF_7 = 6,
|
||||
|
||||
WORGEN_ID_CATHEDRAL_1 = 7,
|
||||
WORGEN_ID_CATHEDRAL_2 = 8,
|
||||
WORGEN_ID_CATHEDRAL_3 = 9,
|
||||
WORGEN_ID_CATHEDRAL_4 = 10,
|
||||
WORGEN_ID_CATHEDRAL_5 = 11,
|
||||
WORGEN_ID_CATHEDRAL_6 = 12,
|
||||
WORGEN_ID_CATHEDRAL_7 = 13,
|
||||
WORGEN_ID_CATHEDRAL_8 = 14,
|
||||
WORGEN_ID_CATHEDRAL_9 = 15,
|
||||
|
||||
NPC_WORGEN_RUNT_SPELL = 35188,
|
||||
};
|
||||
|
||||
static Position const RuntSpellSummonJumpPos = { -1671.915f, 1446.734f, 52.28712f };
|
||||
|
||||
struct npc_worgen_runt : public ScriptedAI
|
||||
{
|
||||
npc_worgen_runt(Creature* creature) : ScriptedAI(creature), _worgenID(0), _wayPointCounter(0), _jumped(false) { }
|
||||
|
||||
void JustEngagedWith(Unit* /*who*/) override
|
||||
{
|
||||
_events.SetPhase(PHASE_COMBAT);
|
||||
}
|
||||
|
||||
void IsSummonedBy(Unit* summoner) override
|
||||
{
|
||||
me->setActive(true); // we are in a phased and cut off map so we're fine to use that here
|
||||
_events.SetPhase(PHASE_ROOF);
|
||||
_events.ScheduleEvent(EVENT_FORCE_DESPAWN, 70s, 0, PHASE_ROOF);
|
||||
_playerGuid = summoner->GetGUID();
|
||||
if (me->GetEntry() == NPC_WORGEN_RUNT_SPELL)
|
||||
me->GetMotionMaster()->MoveJump(RuntSpellSummonJumpPos, 16.0f, 4.371286f);
|
||||
}
|
||||
|
||||
void DoAction(int32 action) override
|
||||
{
|
||||
_worgenID = action;
|
||||
switch (_worgenID) // ugly as fuck but needed for individual controls
|
||||
{
|
||||
// House roof cases
|
||||
case WORGEN_ID_ROOF_1:
|
||||
_wayPointCounter = runtHousePathSize1;
|
||||
me->GetMotionMaster()->MoveSmoothPath(runtHousePathSize1, worgenRuntHousePath1, runtHousePathSize1);
|
||||
break;
|
||||
case WORGEN_ID_ROOF_2:
|
||||
_wayPointCounter = runtHousePathSize2;
|
||||
me->GetMotionMaster()->MoveSmoothPath(runtHousePathSize2, worgenRuntHousePath2, runtHousePathSize2);
|
||||
break;
|
||||
case WORGEN_ID_ROOF_3:
|
||||
_wayPointCounter = runtHousePathSize3;
|
||||
me->GetMotionMaster()->MoveSmoothPath(runtHousePathSize3, worgenRuntHousePath3, runtHousePathSize3);
|
||||
break;
|
||||
case WORGEN_ID_ROOF_4:
|
||||
_wayPointCounter = runtHousePathSize4;
|
||||
me->GetMotionMaster()->MoveSmoothPath(runtHousePathSize4, worgenRuntHousePath4, runtHousePathSize4);
|
||||
break;
|
||||
case WORGEN_ID_ROOF_5:
|
||||
_wayPointCounter = runtHousePathSize5;
|
||||
me->GetMotionMaster()->MoveSmoothPath(runtHousePathSize5, worgenRuntHousePath5, runtHousePathSize5);
|
||||
break;
|
||||
case WORGEN_ID_ROOF_6:
|
||||
_wayPointCounter = runtHousePathSize6;
|
||||
me->GetMotionMaster()->MoveSmoothPath(runtHousePathSize6, worgenRuntHousePath6, runtHousePathSize6);
|
||||
break;
|
||||
case WORGEN_ID_ROOF_7:
|
||||
_wayPointCounter = runtHousePathSize7;
|
||||
me->GetMotionMaster()->MoveSmoothPath(runtHousePathSize7, worgenRuntHousePath7, runtHousePathSize7);
|
||||
break;
|
||||
// Cathedral cases
|
||||
case WORGEN_ID_CATHEDRAL_1:
|
||||
_wayPointCounter = runtCathedralPathSize1;
|
||||
me->GetMotionMaster()->MoveSmoothPath(runtCathedralPathSize1, worgenRuntCathedralPath1, runtCathedralPathSize1);
|
||||
break;
|
||||
case WORGEN_ID_CATHEDRAL_2:
|
||||
_wayPointCounter = runtCathedralPathSize2;
|
||||
me->GetMotionMaster()->MoveSmoothPath(runtCathedralPathSize2, worgenRuntCathedralPath1, runtCathedralPathSize2);
|
||||
break;
|
||||
case WORGEN_ID_CATHEDRAL_3:
|
||||
_wayPointCounter = runtCathedralPathSize3;
|
||||
me->GetMotionMaster()->MoveSmoothPath(runtCathedralPathSize3, worgenRuntCathedralPath1, runtCathedralPathSize3);
|
||||
break;
|
||||
case WORGEN_ID_CATHEDRAL_4:
|
||||
_wayPointCounter = runtCathedralPathSize4;
|
||||
me->GetMotionMaster()->MoveSmoothPath(runtCathedralPathSize4, worgenRuntCathedralPath1, runtCathedralPathSize4);
|
||||
break;
|
||||
case WORGEN_ID_CATHEDRAL_5:
|
||||
_wayPointCounter = runtCathedralPathSize5;
|
||||
me->GetMotionMaster()->MoveSmoothPath(runtCathedralPathSize5, worgenRuntCathedralPath1, runtCathedralPathSize5);
|
||||
break;
|
||||
case WORGEN_ID_CATHEDRAL_6:
|
||||
_wayPointCounter = runtCathedralPathSize6;
|
||||
me->GetMotionMaster()->MoveSmoothPath(runtCathedralPathSize6, worgenRuntCathedralPath1, runtCathedralPathSize6);
|
||||
break;
|
||||
case WORGEN_ID_CATHEDRAL_7:
|
||||
_wayPointCounter = runtCathedralPathSize7;
|
||||
me->GetMotionMaster()->MoveSmoothPath(runtCathedralPathSize7, worgenRuntCathedralPath1, runtCathedralPathSize7);
|
||||
break;
|
||||
case WORGEN_ID_CATHEDRAL_8:
|
||||
_wayPointCounter = runtCathedralPathSize8;
|
||||
me->GetMotionMaster()->MoveSmoothPath(runtCathedralPathSize8, worgenRuntCathedralPath1, runtCathedralPathSize8);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void JustDied(Unit* /*killer*/) override
|
||||
{
|
||||
me->DespawnOrUnsummon(5s);
|
||||
}
|
||||
|
||||
void MovementInform(uint32 type, uint32 pointId) override
|
||||
{
|
||||
if (type == EFFECT_MOTION_TYPE && pointId == _wayPointCounter && !_jumped)
|
||||
{
|
||||
_jumped = true;
|
||||
_events.ScheduleEvent(EVENT_JUMP_TO_PRISON, 1ms);
|
||||
}
|
||||
}
|
||||
|
||||
void UpdateAI(uint32 diff) override
|
||||
{
|
||||
if (!UpdateVictim() && !_events.IsInPhase(PHASE_ROOF))
|
||||
return;
|
||||
|
||||
_events.Update(diff);
|
||||
|
||||
while (uint32 eventId = _events.ExecuteEvent())
|
||||
{
|
||||
switch (eventId)
|
||||
{
|
||||
case EVENT_FORCE_DESPAWN:
|
||||
me->DespawnOrUnsummon();
|
||||
break;
|
||||
case EVENT_JUMP_TO_PRISON:
|
||||
me->GetMotionMaster()->MoveJump(worgenRuntJumpPos[_worgenID], 16.0f, _worgenID < WORGEN_ID_CATHEDRAL_1 ? 19.2911f : frand(3.945607f, 4.852813f));
|
||||
me->SetHomePosition(worgenRuntJumpPos[_worgenID]);
|
||||
_events.ScheduleEvent(EVENT_AGGRO_PLAYER, 2s);
|
||||
break;
|
||||
case EVENT_AGGRO_PLAYER:
|
||||
{
|
||||
Creature* crowley = me->FindNearestCreature(35077, 100.0f, true);
|
||||
Creature* tobias = me->FindNearestCreature(35124, 100.0f, true);
|
||||
|
||||
if (crowley && tobias)
|
||||
{
|
||||
if (me->GetDistance(crowley) <= me->GetDistance(tobias))
|
||||
AttackStart(crowley);
|
||||
else
|
||||
AttackStart(tobias);
|
||||
}
|
||||
else if (crowley)
|
||||
AttackStart(crowley);
|
||||
else if (tobias)
|
||||
AttackStart(tobias);
|
||||
|
||||
break;
|
||||
}
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
DoMeleeAttackIfReady();
|
||||
}
|
||||
private:
|
||||
uint32 _worgenID;
|
||||
uint32 _wayPointCounter;
|
||||
bool _jumped;
|
||||
ObjectGuid _playerGuid;
|
||||
EventMap _events;
|
||||
};
|
||||
|
||||
/*######
|
||||
## Quest 14159 - The Rebel Lord's Arsenal
|
||||
######*/
|
||||
|
||||
enum TheRebelLordsArsena
|
||||
{
|
||||
PHASE_ID_SUMMON = 170,
|
||||
PHASE_ID_WOUND = 171,
|
||||
NPC_LORNA_CROWLEY = 35378,
|
||||
NPC_GENERIC_TRIGGER_LAB = 35374,
|
||||
|
||||
SPELL_SHOOT_INSTAKILL = 67593,
|
||||
SPELL_COSMETIC_ATTACK = 42880,
|
||||
SPELL_PULL_TO = 67357,
|
||||
|
||||
EVENT_COSMETIC_ATTACK = 1,
|
||||
EVENT_JUMP_TO_PLAYER,
|
||||
EVENT_SHOOT_JOSIAH
|
||||
};
|
||||
|
||||
static Position const JosiahJumpPos = { -1796.63f, 1427.73f, 12.4624f };
|
||||
|
||||
struct npc_josiah_avery : public PassiveAI
|
||||
{
|
||||
npc_josiah_avery(Creature* creature) : PassiveAI(creature) { }
|
||||
|
||||
void IsSummonedBy(Unit* summoner) override
|
||||
{
|
||||
PhasingHandler::AddPhase(me, PHASE_ID_SUMMON, true);
|
||||
PhasingHandler::AddPhase(me, PHASE_ID_WOUND, true);
|
||||
_playerGuid = summoner->GetGUID();
|
||||
_events.ScheduleEvent(EVENT_COSMETIC_ATTACK, 500ms);
|
||||
}
|
||||
|
||||
void JustDied(Unit* /*killer*/) override
|
||||
{
|
||||
me->DespawnOrUnsummon(5s);
|
||||
}
|
||||
|
||||
void UpdateAI(uint32 diff) override
|
||||
{
|
||||
_events.Update(diff);
|
||||
|
||||
while (uint32 eventId = _events.ExecuteEvent())
|
||||
{
|
||||
switch (eventId)
|
||||
{
|
||||
case EVENT_COSMETIC_ATTACK:
|
||||
if (Player* player = ObjectAccessor::GetPlayer(*me, _playerGuid))
|
||||
{
|
||||
DoCast(player, SPELL_COSMETIC_ATTACK);
|
||||
if (Creature* lorna = me->FindNearestCreature(NPC_LORNA_CROWLEY, 30.0f, true))
|
||||
if (Creature* labTrigger = lorna->FindNearestCreature(NPC_GENERIC_TRIGGER_LAB, 5.0f, true))
|
||||
labTrigger->CastSpell(player, SPELL_PULL_TO);
|
||||
|
||||
_events.ScheduleEvent(EVENT_JUMP_TO_PLAYER, 1s);
|
||||
}
|
||||
break;
|
||||
case EVENT_JUMP_TO_PLAYER:
|
||||
me->GetMotionMaster()->MoveJump(JosiahJumpPos, 10.0f, 14.18636f);
|
||||
_events.ScheduleEvent(EVENT_SHOOT_JOSIAH, 500ms);
|
||||
break;
|
||||
case EVENT_SHOOT_JOSIAH:
|
||||
if (Creature* lorna = me->FindNearestCreature(NPC_LORNA_CROWLEY, 30.0f, true))
|
||||
lorna->CastSpell(me, SPELL_SHOOT_INSTAKILL, true);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
private:
|
||||
ObjectGuid _playerGuid;
|
||||
EventMap _events;
|
||||
};
|
||||
|
||||
/*######
|
||||
## Quest 14293 - Save Krennan Aranas
|
||||
######*/
|
||||
|
||||
static Position const GreymanesHorseJumpPos = { -1676.16f, 1346.19f, 15.1349f };
|
||||
|
||||
enum GreymanesHorse
|
||||
{
|
||||
SAY_ANNOUNCE_RESCUE = 0,
|
||||
SAY_RESCUED = 0,
|
||||
SAY_TRAPPED = 0,
|
||||
|
||||
PATH_KRENNAN_TREE = 0,
|
||||
PATH_KRENNAN_BACK = 1,
|
||||
|
||||
EVENT_START_PATH_1 = 1,
|
||||
EVENT_START_PATH_2,
|
||||
EVENT_JUMP_TO_KRENNAN,
|
||||
EVENT_ANNOUNCE_RESCUE,
|
||||
EVENT_DISMOUNT_PLAYER,
|
||||
|
||||
NPC_RESCUED_KRENNAN = 35907,
|
||||
NPC_TRAPPED_KRENNAN = 35753,
|
||||
};
|
||||
|
||||
struct npc_greymanes_horse : public VehicleAI
|
||||
{
|
||||
npc_greymanes_horse(Creature* creature) : VehicleAI(creature), _currentPath(PATH_KRENNAN_TREE) { }
|
||||
|
||||
void DamageTaken(Unit* /*attacker*/, uint32& damage) override
|
||||
{
|
||||
damage = 0;
|
||||
}
|
||||
|
||||
void PassengerBoarded(Unit* passenger, int8 /*seatId*/, bool apply) override
|
||||
{
|
||||
if (apply && passenger->GetTypeId() == TYPEID_PLAYER)
|
||||
{
|
||||
me->SetControlled(true, UNIT_STATE_ROOT);
|
||||
_events.ScheduleEvent(EVENT_START_PATH_1, 1s);
|
||||
}
|
||||
else if (apply && passenger->GetEntry() == NPC_RESCUED_KRENNAN)
|
||||
_events.ScheduleEvent(EVENT_START_PATH_2, 1s);
|
||||
}
|
||||
|
||||
void MovementInform(uint32 type, uint32 pointId) override
|
||||
{
|
||||
if (type == EFFECT_MOTION_TYPE && pointId == pathSize1 && _currentPath == PATH_KRENNAN_TREE)
|
||||
_events.ScheduleEvent(EVENT_JUMP_TO_KRENNAN, 1ms);
|
||||
else if (type == EFFECT_MOTION_TYPE && pointId == pathSize2 && _currentPath == PATH_KRENNAN_BACK)
|
||||
_events.ScheduleEvent(EVENT_DISMOUNT_PLAYER, 1ms);
|
||||
}
|
||||
|
||||
void UpdateAI(uint32 diff) override
|
||||
{
|
||||
_events.Update(diff);
|
||||
|
||||
if (me->HasUnitState(UNIT_STATE_CASTING))
|
||||
return;
|
||||
|
||||
while (uint32 eventId = _events.ExecuteEvent())
|
||||
{
|
||||
switch (eventId)
|
||||
{
|
||||
case EVENT_START_PATH_1:
|
||||
me->SetControlled(false, UNIT_STATE_ROOT);
|
||||
_currentPath = PATH_KRENNAN_TREE;
|
||||
me->GetMotionMaster()->MoveSmoothPath(pathSize1, greymanesHorsePath1, pathSize1);
|
||||
break;
|
||||
case EVENT_JUMP_TO_KRENNAN:
|
||||
me->GetMotionMaster()->MoveJump(GreymanesHorseJumpPos, 16.0f, 14.00968f);
|
||||
_events.ScheduleEvent(EVENT_ANNOUNCE_RESCUE, 2s);
|
||||
break;
|
||||
case EVENT_ANNOUNCE_RESCUE:
|
||||
me->SetControlled(true, UNIT_STATE_ROOT);
|
||||
if (Unit* passenger = me->GetVehicleKit()->GetPassenger(0))
|
||||
{
|
||||
Talk(SAY_ANNOUNCE_RESCUE, passenger);
|
||||
if (Creature* krennan = me->FindNearestCreature(NPC_TRAPPED_KRENNAN, 30.0f, true))
|
||||
krennan->AI()->Talk(SAY_TRAPPED, passenger);
|
||||
}
|
||||
break;
|
||||
case EVENT_START_PATH_2:
|
||||
me->SetControlled(true, UNIT_STATE_ROOT);
|
||||
_currentPath = PATH_KRENNAN_BACK;
|
||||
me->GetMotionMaster()->MoveSmoothPath(pathSize2, greymanesHorsePath2, pathSize2);
|
||||
break;
|
||||
case EVENT_DISMOUNT_PLAYER:
|
||||
if (Unit* passenger = me->GetVehicleKit()->GetPassenger(1))
|
||||
{
|
||||
if (Unit* player = me->GetVehicleKit()->GetPassenger(0))
|
||||
{
|
||||
if (Creature* krennan = passenger->ToCreature())
|
||||
{
|
||||
std::vector<Unit*> storedAttackers;
|
||||
|
||||
for (Unit* attacker : me->getAttackers())
|
||||
storedAttackers.push_back(attacker);
|
||||
|
||||
for (Unit* attacker : storedAttackers)
|
||||
{
|
||||
if (Creature* creature = attacker->ToCreature())
|
||||
if (creature->IsAIEnabled())
|
||||
creature->AI()->EnterEvadeMode();
|
||||
}
|
||||
|
||||
player->ExitVehicle();
|
||||
krennan->AddUnitState(UNIT_STATE_ROOT);
|
||||
krennan->ExitVehicle();
|
||||
krennan->AI()->Talk(SAY_RESCUED, me);
|
||||
krennan->DespawnOrUnsummon(7s);
|
||||
}
|
||||
}
|
||||
}
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
private:
|
||||
EventMap _events;
|
||||
uint32 _currentPath;
|
||||
};
|
||||
|
||||
/*######
|
||||
## Quest 14212 - Sacrifices
|
||||
######*/
|
||||
|
||||
enum CrowleysHorse
|
||||
{
|
||||
EVENT_JUMP_OVER_BARRICADES_1 = 1,
|
||||
EVENT_JUMP_OVER_BARRICADES_2,
|
||||
EVENT_MOVE_PATH_MAIN_1,
|
||||
EVENT_MOVE_PATH_MAIN_2,
|
||||
EVENT_MOVE_OFF_PATH = 6,
|
||||
|
||||
PATH_ID_CROWLEYS_HORSE_1 = 352310,
|
||||
PATH_ID_CROWLEYS_HORSE_2 = 352311,
|
||||
PATH_ID_CROWLEYS_HORSE_3 = 444280,
|
||||
|
||||
SPELL_THROW_TORCH = 67063,
|
||||
NPC_CROWLEYS_HORSE_2 = 44428,
|
||||
};
|
||||
|
||||
static Position const CrowleysHorseJumpPos = { -1714.762f, 1673.16f, 20.49182f };
|
||||
static Position const CrowleysHorseJumpPos2 = { -1566.71f, 1708.04f, 20.4849f };
|
||||
|
||||
struct npc_crowleys_horse : public VehicleAI
|
||||
{
|
||||
npc_crowleys_horse(Creature* creature) : VehicleAI(creature), _currentPath(0) { }
|
||||
|
||||
void PassengerBoarded(Unit* passenger, int8 /*seatId*/, bool apply) override
|
||||
{
|
||||
if (apply && passenger->GetTypeId() == TYPEID_PLAYER && me->GetEntry() != NPC_CROWLEYS_HORSE_2)
|
||||
{
|
||||
me->SetControlled(true, UNIT_STATE_ROOT);
|
||||
_events.ScheduleEvent(EVENT_JUMP_OVER_BARRICADES_1, 2s);
|
||||
}
|
||||
else if (apply && passenger->GetTypeId() == TYPEID_PLAYER)
|
||||
{
|
||||
me->SetControlled(true, UNIT_STATE_ROOT);
|
||||
_events.ScheduleEvent(EVENT_MOVE_OFF_PATH, 2s);
|
||||
}
|
||||
}
|
||||
|
||||
void MovementInform(uint32 type, uint32 pointId) override
|
||||
{
|
||||
if (type == WAYPOINT_MOTION_TYPE && pointId == 14 && _currentPath == PATH_ID_CROWLEYS_HORSE_1)
|
||||
_events.ScheduleEvent(EVENT_JUMP_OVER_BARRICADES_2, 1s);
|
||||
else if (type == WAYPOINT_MOTION_TYPE && pointId == 15 && _currentPath == PATH_ID_CROWLEYS_HORSE_2)
|
||||
_events.ScheduleEvent(EVENT_DISMOUNT_PLAYER, 1ms);
|
||||
else if (type == WAYPOINT_MOTION_TYPE && pointId == 16 && _currentPath == PATH_ID_CROWLEYS_HORSE_3)
|
||||
_events.ScheduleEvent(EVENT_DISMOUNT_PLAYER, 1ms);
|
||||
}
|
||||
|
||||
void UpdateAI(uint32 diff) override
|
||||
{
|
||||
_events.Update(diff);
|
||||
|
||||
while (uint32 eventId = _events.ExecuteEvent())
|
||||
{
|
||||
switch (eventId)
|
||||
{
|
||||
case EVENT_JUMP_OVER_BARRICADES_1:
|
||||
me->SetControlled(false, UNIT_STATE_ROOT);
|
||||
me->GetMotionMaster()->MoveJump(CrowleysHorseJumpPos, 16.0f, 18.56182f);
|
||||
_events.ScheduleEvent(EVENT_MOVE_PATH_MAIN_1, 2s);
|
||||
break;
|
||||
case EVENT_MOVE_PATH_MAIN_1:
|
||||
me->GetMotionMaster()->MovePath(PATH_ID_CROWLEYS_HORSE_1, false);
|
||||
_currentPath = PATH_ID_CROWLEYS_HORSE_1;
|
||||
break;
|
||||
case EVENT_JUMP_OVER_BARRICADES_2:
|
||||
me->GetMotionMaster()->MoveJump(CrowleysHorseJumpPos2, 16.0f, 18.56182f);
|
||||
_events.ScheduleEvent(EVENT_MOVE_PATH_MAIN_2, 2s);
|
||||
break;
|
||||
case EVENT_MOVE_PATH_MAIN_2:
|
||||
_currentPath = PATH_ID_CROWLEYS_HORSE_2;
|
||||
me->GetMotionMaster()->MovePath(PATH_ID_CROWLEYS_HORSE_2, false);
|
||||
break;
|
||||
case EVENT_DISMOUNT_PLAYER:
|
||||
{
|
||||
std::set<Unit*> attackersCopy = me->getAttackers();
|
||||
for (Unit* attacker : attackersCopy)
|
||||
{
|
||||
if (Creature* creature = attacker->ToCreature())
|
||||
if (creature->IsAIEnabled())
|
||||
creature->AI()->EnterEvadeMode();
|
||||
}
|
||||
|
||||
me->RemoveAurasDueToSpell(VEHICLE_SPELL_RIDE_HARDCODED);
|
||||
me->DespawnOrUnsummon(5s);
|
||||
break;
|
||||
}
|
||||
case EVENT_MOVE_OFF_PATH:
|
||||
me->SetControlled(false, UNIT_STATE_ROOT);
|
||||
_currentPath = PATH_ID_CROWLEYS_HORSE_3;
|
||||
me->GetMotionMaster()->MovePath(PATH_ID_CROWLEYS_HORSE_3, false);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
private:
|
||||
EventMap _events;
|
||||
uint32 _currentPath;
|
||||
};
|
||||
|
||||
enum GileanCrow
|
||||
{
|
||||
SPELL_GILNEAN_CROW = 93275,
|
||||
EVENT_APPLY_HOVER_BYTES = 1,
|
||||
EVENT_FLY_AWAY_1 = 2,
|
||||
EVENT_FLY_AWAY_2 = 3,
|
||||
|
||||
POINT_NONE = 0,
|
||||
POINT_CROW_FLIGHT = 1
|
||||
};
|
||||
|
||||
struct npc_gilnean_crow : public PassiveAI
|
||||
{
|
||||
npc_gilnean_crow(Creature* creature) : PassiveAI(creature) { }
|
||||
|
||||
void SpellHit(WorldObject* /*caster*/, SpellInfo const* spell) override
|
||||
{
|
||||
if (spell->Id == SPELL_GILNEAN_CROW)
|
||||
_events.ScheduleEvent(EVENT_APPLY_HOVER_BYTES, 500ms);
|
||||
}
|
||||
|
||||
void MovementInform(uint32 type, uint32 pointId) override
|
||||
{
|
||||
if (type == POINT_MOTION_TYPE && pointId == POINT_CROW_FLIGHT)
|
||||
{
|
||||
Position pos = me->GetRandomNearPosition(8.0f);
|
||||
pos.m_positionZ = me->GetPositionZ() + 10.0f;
|
||||
me->GetMotionMaster()->MovePoint(POINT_NONE, pos);
|
||||
me->DespawnOrUnsummon(14s);
|
||||
}
|
||||
}
|
||||
|
||||
void UpdateAI(uint32 diff) override
|
||||
{
|
||||
_events.Update(diff);
|
||||
|
||||
while (uint32 eventId = _events.ExecuteEvent())
|
||||
{
|
||||
switch (eventId)
|
||||
{
|
||||
case EVENT_APPLY_HOVER_BYTES:
|
||||
me->SetStandState(UNIT_STAND_STATE_SIT);
|
||||
me->SetAnimTier(AnimTier::Submerged);
|
||||
_events.ScheduleEvent(EVENT_FLY_AWAY_1, 1s);
|
||||
break;
|
||||
case EVENT_FLY_AWAY_1:
|
||||
{
|
||||
Position pos = me->GetRandomNearPosition(7.0f);
|
||||
pos.m_positionZ = me->GetPositionZ() + 8.0f;
|
||||
me->GetMotionMaster()->MovePoint(POINT_CROW_FLIGHT, pos);
|
||||
break;
|
||||
}
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
private:
|
||||
EventMap _events;
|
||||
};
|
||||
}
|
||||
|
||||
void AddSC_gilneas_chapter_1()
|
||||
{
|
||||
using namespace Gilneas::Chapter1;
|
||||
RegisterCreatureAI(npc_frightened_citizen);
|
||||
RegisterCreatureAI(npc_worgen_runt);
|
||||
RegisterCreatureAI(npc_josiah_avery);
|
||||
RegisterCreatureAI(npc_greymanes_horse);
|
||||
RegisterCreatureAI(npc_crowleys_horse);
|
||||
RegisterCreatureAI(npc_gilnean_crow);
|
||||
}
|
||||
+696
@@ -0,0 +1,696 @@
|
||||
/*
|
||||
* This file is part of the TrinityCore 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 "gilneas.h"
|
||||
#include "ScriptMgr.h"
|
||||
#include "CombatAI.h"
|
||||
#include "MotionMaster.h"
|
||||
#include "ObjectAccessor.h"
|
||||
#include "Player.h"
|
||||
#include "PassiveAI.h"
|
||||
#include "PhasingHandler.h"
|
||||
#include "ScriptedCreature.h"
|
||||
#include "SpellInfo.h"
|
||||
#include "SpellScript.h"
|
||||
#include "TemporarySummon.h"
|
||||
#include "Vehicle.h"
|
||||
|
||||
namespace Gilneas::Chapter1
|
||||
{
|
||||
|
||||
/*######
|
||||
## Quest 14098 - Evacuate the Merchant Square
|
||||
######*/
|
||||
|
||||
enum FrightenedCitizen
|
||||
{
|
||||
SAY_FRIGHTENED_CITIZEN_RESCUE = 0,
|
||||
|
||||
NPC_EVACUATION_STALKER_FIRST = 35830,
|
||||
NPC_EVACUATION_STALKER_NEAR = 35010,
|
||||
NPC_EVACUATION_STALKER_FAR = 35011,
|
||||
NPC_RAMPAGING_WORGEN = 35660,
|
||||
|
||||
CREDIT_35830 = 35830,
|
||||
|
||||
POINT_STALKER_FIRST = 1,
|
||||
POINT_STALKER_NEAR = 2,
|
||||
POINT_STALKER_FAR = 3,
|
||||
|
||||
EVENT_TALK_FRIGHTENED = 1,
|
||||
EVENT_MOVE_TO_NEAR_STALKER = 2,
|
||||
EVENT_MOVE_TO_FAR_STALKER = 3,
|
||||
EVENT_DESPAWN = 4
|
||||
};
|
||||
|
||||
struct npc_frightened_citizen : public PassiveAI
|
||||
{
|
||||
npc_frightened_citizen(Creature* creature) : PassiveAI(creature) { }
|
||||
|
||||
void IsSummonedBy(Unit* /*summoner*/) override
|
||||
{
|
||||
if (Creature* stalkerNear = me->FindNearestCreature(NPC_EVACUATION_STALKER_FIRST, 20.0f))
|
||||
me->GetMotionMaster()->MovePoint(POINT_STALKER_FIRST, stalkerNear->GetPosition(), true);
|
||||
}
|
||||
|
||||
void MovementInform(uint32 type, uint32 id) override
|
||||
{
|
||||
if (type != POINT_MOTION_TYPE)
|
||||
return;
|
||||
|
||||
switch (id)
|
||||
{
|
||||
case POINT_STALKER_FIRST:
|
||||
_events.ScheduleEvent(EVENT_TALK_FRIGHTENED, 1s);
|
||||
break;
|
||||
case POINT_STALKER_NEAR:
|
||||
_events.ScheduleEvent(EVENT_MOVE_TO_FAR_STALKER, 1ms);
|
||||
break;
|
||||
case POINT_STALKER_FAR:
|
||||
_events.ScheduleEvent(EVENT_DESPAWN, 1ms);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void UpdateAI(uint32 diff) override
|
||||
{
|
||||
_events.Update(diff);
|
||||
|
||||
while (uint32 eventId = _events.ExecuteEvent())
|
||||
{
|
||||
switch (eventId)
|
||||
{
|
||||
case EVENT_TALK_FRIGHTENED:
|
||||
if (Unit* summoner = me->ToTempSummon()->GetSummoner())
|
||||
{
|
||||
if (Player* player = summoner->ToPlayer())
|
||||
{
|
||||
player->KilledMonsterCredit(CREDIT_35830);
|
||||
Talk(SAY_FRIGHTENED_CITIZEN_RESCUE, summoner);
|
||||
}
|
||||
}
|
||||
_events.ScheduleEvent(EVENT_MOVE_TO_NEAR_STALKER, 2s);
|
||||
break;
|
||||
case EVENT_MOVE_TO_NEAR_STALKER:
|
||||
if (Creature* stalker = me->FindNearestCreature(NPC_EVACUATION_STALKER_FAR, 50.0f))
|
||||
me->GetMotionMaster()->MovePoint(POINT_STALKER_FAR, stalker->GetPosition(), true);
|
||||
else if (Creature* stalker = me->FindNearestCreature(NPC_EVACUATION_STALKER_NEAR, 100.0f))
|
||||
me->GetMotionMaster()->MovePoint(POINT_STALKER_NEAR, stalker->GetPosition(), true);
|
||||
break;
|
||||
case EVENT_MOVE_TO_FAR_STALKER:
|
||||
if (Creature* stalker = me->FindNearestCreature(NPC_EVACUATION_STALKER_FAR, 500.0f))
|
||||
me->GetMotionMaster()->MovePoint(POINT_STALKER_FAR, stalker->GetPosition(), true);
|
||||
break;
|
||||
case EVENT_DESPAWN:
|
||||
me->DespawnOrUnsummon();
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
private:
|
||||
EventMap _events;
|
||||
};
|
||||
|
||||
/*######
|
||||
## Quest 14154 - By the Skin of his Teeth
|
||||
######*/
|
||||
|
||||
enum WorgenRunt
|
||||
{
|
||||
EVENT_JUMP_TO_PRISON = 1,
|
||||
EVENT_AGGRO_PLAYER = 2,
|
||||
EVENT_FORCE_DESPAWN = 3,
|
||||
|
||||
PHASE_ROOF = 0,
|
||||
PHASE_COMBAT = 1,
|
||||
|
||||
WORGEN_ID_ROOF_1 = 0,
|
||||
WORGEN_ID_ROOF_2 = 1,
|
||||
WORGEN_ID_ROOF_3 = 2,
|
||||
WORGEN_ID_ROOF_4 = 3,
|
||||
WORGEN_ID_ROOF_5 = 4,
|
||||
WORGEN_ID_ROOF_6 = 5,
|
||||
WORGEN_ID_ROOF_7 = 6,
|
||||
|
||||
WORGEN_ID_CATHEDRAL_1 = 7,
|
||||
WORGEN_ID_CATHEDRAL_2 = 8,
|
||||
WORGEN_ID_CATHEDRAL_3 = 9,
|
||||
WORGEN_ID_CATHEDRAL_4 = 10,
|
||||
WORGEN_ID_CATHEDRAL_5 = 11,
|
||||
WORGEN_ID_CATHEDRAL_6 = 12,
|
||||
WORGEN_ID_CATHEDRAL_7 = 13,
|
||||
WORGEN_ID_CATHEDRAL_8 = 14,
|
||||
WORGEN_ID_CATHEDRAL_9 = 15,
|
||||
|
||||
NPC_WORGEN_RUNT_SPELL = 35188,
|
||||
};
|
||||
|
||||
static Position const RuntSpellSummonJumpPos = { -1671.915f, 1446.734f, 52.28712f };
|
||||
|
||||
struct npc_worgen_runt : public ScriptedAI
|
||||
{
|
||||
npc_worgen_runt(Creature* creature) : ScriptedAI(creature), _worgenID(0), _wayPointCounter(0), _jumped(false) { }
|
||||
|
||||
void JustEngagedWith(Unit* /*who*/) override
|
||||
{
|
||||
_events.SetPhase(PHASE_COMBAT);
|
||||
}
|
||||
|
||||
void IsSummonedBy(Unit* summoner) override
|
||||
{
|
||||
me->setActive(true); // we are in a phased and cut off map so we're fine to use that here
|
||||
_events.SetPhase(PHASE_ROOF);
|
||||
_events.ScheduleEvent(EVENT_FORCE_DESPAWN, 70s, 0, PHASE_ROOF);
|
||||
_playerGuid = summoner->GetGUID();
|
||||
if (me->GetEntry() == NPC_WORGEN_RUNT_SPELL)
|
||||
me->GetMotionMaster()->MoveJump(RuntSpellSummonJumpPos, 16.0f, 4.371286f);
|
||||
}
|
||||
|
||||
void DoAction(int32 action) override
|
||||
{
|
||||
_worgenID = action;
|
||||
switch (_worgenID) // ugly as fuck but needed for individual controls
|
||||
{
|
||||
// House roof cases
|
||||
case WORGEN_ID_ROOF_1:
|
||||
_wayPointCounter = runtHousePathSize1;
|
||||
me->GetMotionMaster()->MoveSmoothPath(runtHousePathSize1, worgenRuntHousePath1, runtHousePathSize1);
|
||||
break;
|
||||
case WORGEN_ID_ROOF_2:
|
||||
_wayPointCounter = runtHousePathSize2;
|
||||
me->GetMotionMaster()->MoveSmoothPath(runtHousePathSize2, worgenRuntHousePath2, runtHousePathSize2);
|
||||
break;
|
||||
case WORGEN_ID_ROOF_3:
|
||||
_wayPointCounter = runtHousePathSize3;
|
||||
me->GetMotionMaster()->MoveSmoothPath(runtHousePathSize3, worgenRuntHousePath3, runtHousePathSize3);
|
||||
break;
|
||||
case WORGEN_ID_ROOF_4:
|
||||
_wayPointCounter = runtHousePathSize4;
|
||||
me->GetMotionMaster()->MoveSmoothPath(runtHousePathSize4, worgenRuntHousePath4, runtHousePathSize4);
|
||||
break;
|
||||
case WORGEN_ID_ROOF_5:
|
||||
_wayPointCounter = runtHousePathSize5;
|
||||
me->GetMotionMaster()->MoveSmoothPath(runtHousePathSize5, worgenRuntHousePath5, runtHousePathSize5);
|
||||
break;
|
||||
case WORGEN_ID_ROOF_6:
|
||||
_wayPointCounter = runtHousePathSize6;
|
||||
me->GetMotionMaster()->MoveSmoothPath(runtHousePathSize6, worgenRuntHousePath6, runtHousePathSize6);
|
||||
break;
|
||||
case WORGEN_ID_ROOF_7:
|
||||
_wayPointCounter = runtHousePathSize7;
|
||||
me->GetMotionMaster()->MoveSmoothPath(runtHousePathSize7, worgenRuntHousePath7, runtHousePathSize7);
|
||||
break;
|
||||
// Cathedral cases
|
||||
case WORGEN_ID_CATHEDRAL_1:
|
||||
_wayPointCounter = runtCathedralPathSize1;
|
||||
me->GetMotionMaster()->MoveSmoothPath(runtCathedralPathSize1, worgenRuntCathedralPath1, runtCathedralPathSize1);
|
||||
break;
|
||||
case WORGEN_ID_CATHEDRAL_2:
|
||||
_wayPointCounter = runtCathedralPathSize2;
|
||||
me->GetMotionMaster()->MoveSmoothPath(runtCathedralPathSize2, worgenRuntCathedralPath1, runtCathedralPathSize2);
|
||||
break;
|
||||
case WORGEN_ID_CATHEDRAL_3:
|
||||
_wayPointCounter = runtCathedralPathSize3;
|
||||
me->GetMotionMaster()->MoveSmoothPath(runtCathedralPathSize3, worgenRuntCathedralPath1, runtCathedralPathSize3);
|
||||
break;
|
||||
case WORGEN_ID_CATHEDRAL_4:
|
||||
_wayPointCounter = runtCathedralPathSize4;
|
||||
me->GetMotionMaster()->MoveSmoothPath(runtCathedralPathSize4, worgenRuntCathedralPath1, runtCathedralPathSize4);
|
||||
break;
|
||||
case WORGEN_ID_CATHEDRAL_5:
|
||||
_wayPointCounter = runtCathedralPathSize5;
|
||||
me->GetMotionMaster()->MoveSmoothPath(runtCathedralPathSize5, worgenRuntCathedralPath1, runtCathedralPathSize5);
|
||||
break;
|
||||
case WORGEN_ID_CATHEDRAL_6:
|
||||
_wayPointCounter = runtCathedralPathSize6;
|
||||
me->GetMotionMaster()->MoveSmoothPath(runtCathedralPathSize6, worgenRuntCathedralPath1, runtCathedralPathSize6);
|
||||
break;
|
||||
case WORGEN_ID_CATHEDRAL_7:
|
||||
_wayPointCounter = runtCathedralPathSize7;
|
||||
me->GetMotionMaster()->MoveSmoothPath(runtCathedralPathSize7, worgenRuntCathedralPath1, runtCathedralPathSize7);
|
||||
break;
|
||||
case WORGEN_ID_CATHEDRAL_8:
|
||||
_wayPointCounter = runtCathedralPathSize8;
|
||||
me->GetMotionMaster()->MoveSmoothPath(runtCathedralPathSize8, worgenRuntCathedralPath1, runtCathedralPathSize8);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void JustDied(Unit* /*killer*/) override
|
||||
{
|
||||
me->DespawnOrUnsummon(5s);
|
||||
}
|
||||
|
||||
void MovementInform(uint32 type, uint32 pointId) override
|
||||
{
|
||||
if (type == EFFECT_MOTION_TYPE && pointId == _wayPointCounter && !_jumped)
|
||||
{
|
||||
_jumped = true;
|
||||
_events.ScheduleEvent(EVENT_JUMP_TO_PRISON, 1ms);
|
||||
}
|
||||
}
|
||||
|
||||
void UpdateAI(uint32 diff) override
|
||||
{
|
||||
if (!UpdateVictim() && !_events.IsInPhase(PHASE_ROOF))
|
||||
return;
|
||||
|
||||
_events.Update(diff);
|
||||
|
||||
while (uint32 eventId = _events.ExecuteEvent())
|
||||
{
|
||||
switch (eventId)
|
||||
{
|
||||
case EVENT_FORCE_DESPAWN:
|
||||
me->DespawnOrUnsummon();
|
||||
break;
|
||||
case EVENT_JUMP_TO_PRISON:
|
||||
me->GetMotionMaster()->MoveJump(worgenRuntJumpPos[_worgenID], 16.0f, _worgenID < WORGEN_ID_CATHEDRAL_1 ? 19.2911f : frand(3.945607f, 4.852813f));
|
||||
me->SetHomePosition(worgenRuntJumpPos[_worgenID]);
|
||||
_events.ScheduleEvent(EVENT_AGGRO_PLAYER, 2s);
|
||||
break;
|
||||
case EVENT_AGGRO_PLAYER:
|
||||
{
|
||||
Creature* crowley = me->FindNearestCreature(35077, 100.0f, true);
|
||||
Creature* tobias = me->FindNearestCreature(35124, 100.0f, true);
|
||||
|
||||
if (crowley && tobias)
|
||||
{
|
||||
if (me->GetDistance(crowley) <= me->GetDistance(tobias))
|
||||
AttackStart(crowley);
|
||||
else
|
||||
AttackStart(tobias);
|
||||
}
|
||||
else if (crowley)
|
||||
AttackStart(crowley);
|
||||
else if (tobias)
|
||||
AttackStart(tobias);
|
||||
|
||||
break;
|
||||
}
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
DoMeleeAttackIfReady();
|
||||
}
|
||||
private:
|
||||
uint32 _worgenID;
|
||||
uint32 _wayPointCounter;
|
||||
bool _jumped;
|
||||
ObjectGuid _playerGuid;
|
||||
EventMap _events;
|
||||
};
|
||||
|
||||
/*######
|
||||
## Quest 14159 - The Rebel Lord's Arsenal
|
||||
######*/
|
||||
|
||||
enum TheRebelLordsArsena
|
||||
{
|
||||
PHASE_ID_SUMMON = 170,
|
||||
PHASE_ID_WOUND = 171,
|
||||
NPC_LORNA_CROWLEY = 35378,
|
||||
NPC_GENERIC_TRIGGER_LAB = 35374,
|
||||
|
||||
SPELL_SHOOT_INSTAKILL = 67593,
|
||||
SPELL_COSMETIC_ATTACK = 42880,
|
||||
SPELL_PULL_TO = 67357,
|
||||
|
||||
EVENT_COSMETIC_ATTACK = 1,
|
||||
EVENT_JUMP_TO_PLAYER,
|
||||
EVENT_SHOOT_JOSIAH
|
||||
};
|
||||
|
||||
static Position const JosiahJumpPos = { -1796.63f, 1427.73f, 12.4624f };
|
||||
|
||||
struct npc_josiah_avery : public PassiveAI
|
||||
{
|
||||
npc_josiah_avery(Creature* creature) : PassiveAI(creature) { }
|
||||
|
||||
void IsSummonedBy(Unit* summoner) override
|
||||
{
|
||||
PhasingHandler::AddPhase(me, PHASE_ID_SUMMON, true);
|
||||
PhasingHandler::AddPhase(me, PHASE_ID_WOUND, true);
|
||||
_playerGuid = summoner->GetGUID();
|
||||
_events.ScheduleEvent(EVENT_COSMETIC_ATTACK, 500ms);
|
||||
}
|
||||
|
||||
void JustDied(Unit* /*killer*/) override
|
||||
{
|
||||
me->DespawnOrUnsummon(5s);
|
||||
}
|
||||
|
||||
void UpdateAI(uint32 diff) override
|
||||
{
|
||||
_events.Update(diff);
|
||||
|
||||
while (uint32 eventId = _events.ExecuteEvent())
|
||||
{
|
||||
switch (eventId)
|
||||
{
|
||||
case EVENT_COSMETIC_ATTACK:
|
||||
if (Player* player = ObjectAccessor::GetPlayer(*me, _playerGuid))
|
||||
{
|
||||
DoCast(player, SPELL_COSMETIC_ATTACK);
|
||||
if (Creature* lorna = me->FindNearestCreature(NPC_LORNA_CROWLEY, 30.0f, true))
|
||||
if (Creature* labTrigger = lorna->FindNearestCreature(NPC_GENERIC_TRIGGER_LAB, 5.0f, true))
|
||||
labTrigger->CastSpell(player, SPELL_PULL_TO);
|
||||
|
||||
_events.ScheduleEvent(EVENT_JUMP_TO_PLAYER, 1s);
|
||||
}
|
||||
break;
|
||||
case EVENT_JUMP_TO_PLAYER:
|
||||
me->GetMotionMaster()->MoveJump(JosiahJumpPos, 10.0f, 14.18636f);
|
||||
_events.ScheduleEvent(EVENT_SHOOT_JOSIAH, 500ms);
|
||||
break;
|
||||
case EVENT_SHOOT_JOSIAH:
|
||||
if (Creature* lorna = me->FindNearestCreature(NPC_LORNA_CROWLEY, 30.0f, true))
|
||||
lorna->CastSpell(me, SPELL_SHOOT_INSTAKILL, true);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
private:
|
||||
ObjectGuid _playerGuid;
|
||||
EventMap _events;
|
||||
};
|
||||
|
||||
/*######
|
||||
## Quest 14293 - Save Krennan Aranas
|
||||
######*/
|
||||
|
||||
static Position const GreymanesHorseJumpPos = { -1676.16f, 1346.19f, 15.1349f };
|
||||
|
||||
enum GreymanesHorse
|
||||
{
|
||||
SAY_ANNOUNCE_RESCUE = 0,
|
||||
SAY_RESCUED = 0,
|
||||
SAY_TRAPPED = 0,
|
||||
|
||||
PATH_KRENNAN_TREE = 0,
|
||||
PATH_KRENNAN_BACK = 1,
|
||||
|
||||
EVENT_START_PATH_1 = 1,
|
||||
EVENT_START_PATH_2,
|
||||
EVENT_JUMP_TO_KRENNAN,
|
||||
EVENT_ANNOUNCE_RESCUE,
|
||||
EVENT_DISMOUNT_PLAYER,
|
||||
|
||||
NPC_RESCUED_KRENNAN = 35907,
|
||||
NPC_TRAPPED_KRENNAN = 35753,
|
||||
};
|
||||
|
||||
struct npc_greymanes_horse : public VehicleAI
|
||||
{
|
||||
npc_greymanes_horse(Creature* creature) : VehicleAI(creature), _currentPath(PATH_KRENNAN_TREE) { }
|
||||
|
||||
void PassengerBoarded(Unit* passenger, int8 /*seatId*/, bool apply) override
|
||||
{
|
||||
if (apply && passenger->GetTypeId() == TYPEID_PLAYER)
|
||||
{
|
||||
me->SetControlled(true, UNIT_STATE_ROOT);
|
||||
_events.ScheduleEvent(EVENT_START_PATH_1, 1s);
|
||||
}
|
||||
else if (apply && passenger->GetEntry() == NPC_RESCUED_KRENNAN)
|
||||
_events.ScheduleEvent(EVENT_START_PATH_2, 1s);
|
||||
}
|
||||
|
||||
void MovementInform(uint32 type, uint32 pointId) override
|
||||
{
|
||||
if (type == EFFECT_MOTION_TYPE && pointId == pathSize1 && _currentPath == PATH_KRENNAN_TREE)
|
||||
_events.ScheduleEvent(EVENT_JUMP_TO_KRENNAN, 1ms);
|
||||
else if (type == EFFECT_MOTION_TYPE && pointId == pathSize2 && _currentPath == PATH_KRENNAN_BACK)
|
||||
_events.ScheduleEvent(EVENT_DISMOUNT_PLAYER, 1ms);
|
||||
}
|
||||
|
||||
void UpdateAI(uint32 diff) override
|
||||
{
|
||||
_events.Update(diff);
|
||||
|
||||
if (me->HasUnitState(UNIT_STATE_CASTING))
|
||||
return;
|
||||
|
||||
while (uint32 eventId = _events.ExecuteEvent())
|
||||
{
|
||||
switch (eventId)
|
||||
{
|
||||
case EVENT_START_PATH_1:
|
||||
me->SetControlled(false, UNIT_STATE_ROOT);
|
||||
_currentPath = PATH_KRENNAN_TREE;
|
||||
me->GetMotionMaster()->MoveSmoothPath(pathSize1, greymanesHorsePath1, pathSize1);
|
||||
break;
|
||||
case EVENT_JUMP_TO_KRENNAN:
|
||||
me->GetMotionMaster()->MoveJump(GreymanesHorseJumpPos, 16.0f, 14.00968f);
|
||||
_events.ScheduleEvent(EVENT_ANNOUNCE_RESCUE, 2s);
|
||||
break;
|
||||
case EVENT_ANNOUNCE_RESCUE:
|
||||
me->SetControlled(true, UNIT_STATE_ROOT);
|
||||
if (Unit* passenger = me->GetVehicleKit()->GetPassenger(0))
|
||||
{
|
||||
Talk(SAY_ANNOUNCE_RESCUE, passenger);
|
||||
if (Creature* krennan = me->FindNearestCreature(NPC_TRAPPED_KRENNAN, 30.0f, true))
|
||||
krennan->AI()->Talk(SAY_TRAPPED, passenger);
|
||||
}
|
||||
break;
|
||||
case EVENT_START_PATH_2:
|
||||
me->SetControlled(true, UNIT_STATE_ROOT);
|
||||
_currentPath = PATH_KRENNAN_BACK;
|
||||
me->GetMotionMaster()->MoveSmoothPath(pathSize2, greymanesHorsePath2, pathSize2);
|
||||
break;
|
||||
case EVENT_DISMOUNT_PLAYER:
|
||||
if (Unit* passenger = me->GetVehicleKit()->GetPassenger(1))
|
||||
{
|
||||
if (Unit* player = me->GetVehicleKit()->GetPassenger(0))
|
||||
{
|
||||
if (Creature* krennan = passenger->ToCreature())
|
||||
{
|
||||
std::vector<Unit*> storedAttackers;
|
||||
|
||||
for (Unit* attacker : me->getAttackers())
|
||||
storedAttackers.push_back(attacker);
|
||||
|
||||
for (Unit* attacker : storedAttackers)
|
||||
{
|
||||
if (Creature* creature = attacker->ToCreature())
|
||||
if (creature->IsAIEnabled())
|
||||
creature->AI()->EnterEvadeMode();
|
||||
}
|
||||
|
||||
player->ExitVehicle();
|
||||
krennan->AddUnitState(UNIT_STATE_ROOT);
|
||||
krennan->ExitVehicle();
|
||||
krennan->AI()->Talk(SAY_RESCUED, me);
|
||||
krennan->DespawnOrUnsummon(7s);
|
||||
}
|
||||
}
|
||||
}
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
private:
|
||||
EventMap _events;
|
||||
uint32 _currentPath;
|
||||
};
|
||||
|
||||
/*######
|
||||
## Quest 14212 - Sacrifices
|
||||
######*/
|
||||
|
||||
enum CrowleysHorse
|
||||
{
|
||||
EVENT_JUMP_OVER_BARRICADES_1 = 1,
|
||||
EVENT_JUMP_OVER_BARRICADES_2,
|
||||
EVENT_MOVE_PATH_MAIN_1,
|
||||
EVENT_MOVE_PATH_MAIN_2,
|
||||
EVENT_MOVE_OFF_PATH = 6,
|
||||
|
||||
PATH_ID_CROWLEYS_HORSE_1 = 352310,
|
||||
PATH_ID_CROWLEYS_HORSE_2 = 352311,
|
||||
PATH_ID_CROWLEYS_HORSE_3 = 444280,
|
||||
|
||||
SPELL_THROW_TORCH = 67063,
|
||||
NPC_CROWLEYS_HORSE_2 = 44428,
|
||||
};
|
||||
|
||||
static Position const CrowleysHorseJumpPos = { -1714.762f, 1673.16f, 20.49182f };
|
||||
static Position const CrowleysHorseJumpPos2 = { -1566.71f, 1708.04f, 20.4849f };
|
||||
|
||||
struct npc_crowleys_horse : public VehicleAI
|
||||
{
|
||||
npc_crowleys_horse(Creature* creature) : VehicleAI(creature), _currentPath(0) { }
|
||||
|
||||
void PassengerBoarded(Unit* passenger, int8 /*seatId*/, bool apply) override
|
||||
{
|
||||
if (apply && passenger->GetTypeId() == TYPEID_PLAYER && me->GetEntry() != NPC_CROWLEYS_HORSE_2)
|
||||
{
|
||||
me->SetControlled(true, UNIT_STATE_ROOT);
|
||||
_events.ScheduleEvent(EVENT_JUMP_OVER_BARRICADES_1, 2s);
|
||||
}
|
||||
else if (apply && passenger->GetTypeId() == TYPEID_PLAYER)
|
||||
{
|
||||
me->SetControlled(true, UNIT_STATE_ROOT);
|
||||
_events.ScheduleEvent(EVENT_MOVE_OFF_PATH, 2s);
|
||||
}
|
||||
}
|
||||
|
||||
void MovementInform(uint32 type, uint32 pointId) override
|
||||
{
|
||||
if (type == WAYPOINT_MOTION_TYPE && pointId == 14 && _currentPath == PATH_ID_CROWLEYS_HORSE_1)
|
||||
_events.ScheduleEvent(EVENT_JUMP_OVER_BARRICADES_2, 1s);
|
||||
else if (type == WAYPOINT_MOTION_TYPE && pointId == 15 && _currentPath == PATH_ID_CROWLEYS_HORSE_2)
|
||||
_events.ScheduleEvent(EVENT_DISMOUNT_PLAYER, 1ms);
|
||||
else if (type == WAYPOINT_MOTION_TYPE && pointId == 16 && _currentPath == PATH_ID_CROWLEYS_HORSE_3)
|
||||
_events.ScheduleEvent(EVENT_DISMOUNT_PLAYER, 1ms);
|
||||
}
|
||||
|
||||
void UpdateAI(uint32 diff) override
|
||||
{
|
||||
_events.Update(diff);
|
||||
|
||||
while (uint32 eventId = _events.ExecuteEvent())
|
||||
{
|
||||
switch (eventId)
|
||||
{
|
||||
case EVENT_JUMP_OVER_BARRICADES_1:
|
||||
me->SetControlled(false, UNIT_STATE_ROOT);
|
||||
me->GetMotionMaster()->MoveJump(CrowleysHorseJumpPos, 16.0f, 18.56182f);
|
||||
_events.ScheduleEvent(EVENT_MOVE_PATH_MAIN_1, 2s);
|
||||
break;
|
||||
case EVENT_MOVE_PATH_MAIN_1:
|
||||
me->GetMotionMaster()->MovePath(PATH_ID_CROWLEYS_HORSE_1, false);
|
||||
_currentPath = PATH_ID_CROWLEYS_HORSE_1;
|
||||
break;
|
||||
case EVENT_JUMP_OVER_BARRICADES_2:
|
||||
me->GetMotionMaster()->MoveJump(CrowleysHorseJumpPos2, 16.0f, 18.56182f);
|
||||
_events.ScheduleEvent(EVENT_MOVE_PATH_MAIN_2, 2s);
|
||||
break;
|
||||
case EVENT_MOVE_PATH_MAIN_2:
|
||||
_currentPath = PATH_ID_CROWLEYS_HORSE_2;
|
||||
me->GetMotionMaster()->MovePath(PATH_ID_CROWLEYS_HORSE_2, false);
|
||||
break;
|
||||
case EVENT_DISMOUNT_PLAYER:
|
||||
{
|
||||
std::set<Unit*> attackersCopy = me->getAttackers();
|
||||
for (Unit* attacker : attackersCopy)
|
||||
{
|
||||
if (Creature* creature = attacker->ToCreature())
|
||||
if (creature->IsAIEnabled())
|
||||
creature->AI()->EnterEvadeMode();
|
||||
}
|
||||
|
||||
me->RemoveAurasDueToSpell(VEHICLE_SPELL_RIDE_HARDCODED);
|
||||
me->DespawnOrUnsummon(5s);
|
||||
break;
|
||||
}
|
||||
case EVENT_MOVE_OFF_PATH:
|
||||
me->SetControlled(false, UNIT_STATE_ROOT);
|
||||
_currentPath = PATH_ID_CROWLEYS_HORSE_3;
|
||||
me->GetMotionMaster()->MovePath(PATH_ID_CROWLEYS_HORSE_3, false);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
private:
|
||||
EventMap _events;
|
||||
uint32 _currentPath;
|
||||
};
|
||||
|
||||
enum GileanCrow
|
||||
{
|
||||
SPELL_GILNEAN_CROW = 93275,
|
||||
EVENT_APPLY_HOVER_BYTES = 1,
|
||||
EVENT_FLY_AWAY_1 = 2,
|
||||
EVENT_FLY_AWAY_2 = 3,
|
||||
|
||||
POINT_NONE = 0,
|
||||
POINT_CROW_FLIGHT = 1
|
||||
};
|
||||
|
||||
struct npc_gilnean_crow : public PassiveAI
|
||||
{
|
||||
npc_gilnean_crow(Creature* creature) : PassiveAI(creature) { }
|
||||
|
||||
void SpellHit(WorldObject* /*caster*/, SpellInfo const* spell) override
|
||||
{
|
||||
if (spell->Id == SPELL_GILNEAN_CROW)
|
||||
_events.ScheduleEvent(EVENT_APPLY_HOVER_BYTES, 500ms);
|
||||
}
|
||||
|
||||
void MovementInform(uint32 type, uint32 pointId) override
|
||||
{
|
||||
if (type == POINT_MOTION_TYPE && pointId == POINT_CROW_FLIGHT)
|
||||
{
|
||||
Position pos = me->GetRandomNearPosition(8.0f);
|
||||
pos.m_positionZ = me->GetPositionZ() + 10.0f;
|
||||
me->GetMotionMaster()->MovePoint(POINT_NONE, pos);
|
||||
me->DespawnOrUnsummon(14s);
|
||||
}
|
||||
}
|
||||
|
||||
void UpdateAI(uint32 diff) override
|
||||
{
|
||||
_events.Update(diff);
|
||||
|
||||
while (uint32 eventId = _events.ExecuteEvent())
|
||||
{
|
||||
switch (eventId)
|
||||
{
|
||||
case EVENT_APPLY_HOVER_BYTES:
|
||||
me->SetStandState(UNIT_STAND_STATE_SIT);
|
||||
me->SetAnimTier(AnimTier::Submerged);
|
||||
_events.ScheduleEvent(EVENT_FLY_AWAY_1, 1s);
|
||||
break;
|
||||
case EVENT_FLY_AWAY_1:
|
||||
{
|
||||
Position pos = me->GetRandomNearPosition(7.0f);
|
||||
pos.m_positionZ = me->GetPositionZ() + 8.0f;
|
||||
me->GetMotionMaster()->MovePoint(POINT_CROW_FLIGHT, pos);
|
||||
break;
|
||||
}
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
private:
|
||||
EventMap _events;
|
||||
};
|
||||
}
|
||||
|
||||
void AddSC_gilneas_chapter_1()
|
||||
{
|
||||
using namespace Gilneas::Chapter1;
|
||||
RegisterCreatureAI(npc_frightened_citizen);
|
||||
RegisterCreatureAI(npc_worgen_runt);
|
||||
RegisterCreatureAI(npc_josiah_avery);
|
||||
RegisterCreatureAI(npc_greymanes_horse);
|
||||
RegisterCreatureAI(npc_crowleys_horse);
|
||||
RegisterCreatureAI(npc_gilnean_crow);
|
||||
}
|
||||
+704
@@ -0,0 +1,704 @@
|
||||
/*
|
||||
* This file is part of the TrinityCore 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 "gilneas.h"
|
||||
#include "ScriptMgr.h"
|
||||
#include "CombatAI.h"
|
||||
#include "MotionMaster.h"
|
||||
#include "ObjectAccessor.h"
|
||||
#include "Player.h"
|
||||
#include "PassiveAI.h"
|
||||
#include "PhasingHandler.h"
|
||||
#include "ScriptedCreature.h"
|
||||
#include "SpellInfo.h"
|
||||
#include "SpellScript.h"
|
||||
#include "TemporarySummon.h"
|
||||
#include "Vehicle.h"
|
||||
|
||||
namespace Gilneas::Chapter1
|
||||
{
|
||||
|
||||
/*######
|
||||
## Quest 14098 - Evacuate the Merchant Square
|
||||
######*/
|
||||
|
||||
enum FrightenedCitizen
|
||||
{
|
||||
SAY_FRIGHTENED_CITIZEN_RESCUE = 0,
|
||||
|
||||
NPC_EVACUATION_STALKER_FIRST = 35830,
|
||||
NPC_EVACUATION_STALKER_NEAR = 35010,
|
||||
NPC_EVACUATION_STALKER_FAR = 35011,
|
||||
NPC_RAMPAGING_WORGEN = 35660,
|
||||
|
||||
CREDIT_35830 = 35830,
|
||||
|
||||
POINT_STALKER_FIRST = 1,
|
||||
POINT_STALKER_NEAR = 2,
|
||||
POINT_STALKER_FAR = 3,
|
||||
|
||||
EVENT_TALK_FRIGHTENED = 1,
|
||||
EVENT_MOVE_TO_NEAR_STALKER = 2,
|
||||
EVENT_MOVE_TO_FAR_STALKER = 3,
|
||||
EVENT_DESPAWN = 4
|
||||
};
|
||||
|
||||
struct npc_frightened_citizen : public PassiveAI
|
||||
{
|
||||
npc_frightened_citizen(Creature* creature) : PassiveAI(creature) { }
|
||||
|
||||
void IsSummonedBy(Unit* /*summoner*/) override
|
||||
{
|
||||
if (Creature* stalkerNear = me->FindNearestCreature(NPC_EVACUATION_STALKER_FIRST, 20.0f))
|
||||
me->GetMotionMaster()->MovePoint(POINT_STALKER_FIRST, stalkerNear->GetPosition(), true);
|
||||
}
|
||||
|
||||
void MovementInform(uint32 type, uint32 id) override
|
||||
{
|
||||
if (type != POINT_MOTION_TYPE)
|
||||
return;
|
||||
|
||||
switch (id)
|
||||
{
|
||||
case POINT_STALKER_FIRST:
|
||||
_events.ScheduleEvent(EVENT_TALK_FRIGHTENED, 1s);
|
||||
break;
|
||||
case POINT_STALKER_NEAR:
|
||||
_events.ScheduleEvent(EVENT_MOVE_TO_FAR_STALKER, 1ms);
|
||||
break;
|
||||
case POINT_STALKER_FAR:
|
||||
_events.ScheduleEvent(EVENT_DESPAWN, 1ms);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void UpdateAI(uint32 diff) override
|
||||
{
|
||||
_events.Update(diff);
|
||||
|
||||
while (uint32 eventId = _events.ExecuteEvent())
|
||||
{
|
||||
switch (eventId)
|
||||
{
|
||||
case EVENT_TALK_FRIGHTENED:
|
||||
if (Unit* summoner = me->ToTempSummon()->GetSummoner())
|
||||
{
|
||||
if (Player* player = summoner->ToPlayer())
|
||||
{
|
||||
player->KilledMonsterCredit(CREDIT_35830);
|
||||
Talk(SAY_FRIGHTENED_CITIZEN_RESCUE, summoner);
|
||||
}
|
||||
}
|
||||
_events.ScheduleEvent(EVENT_MOVE_TO_NEAR_STALKER, 2s);
|
||||
break;
|
||||
case EVENT_MOVE_TO_NEAR_STALKER:
|
||||
if (Creature* stalker = me->FindNearestCreature(NPC_EVACUATION_STALKER_FAR, 50.0f))
|
||||
me->GetMotionMaster()->MovePoint(POINT_STALKER_FAR, stalker->GetPosition(), true);
|
||||
else if (Creature* stalker = me->FindNearestCreature(NPC_EVACUATION_STALKER_NEAR, 100.0f))
|
||||
me->GetMotionMaster()->MovePoint(POINT_STALKER_NEAR, stalker->GetPosition(), true);
|
||||
break;
|
||||
case EVENT_MOVE_TO_FAR_STALKER:
|
||||
if (Creature* stalker = me->FindNearestCreature(NPC_EVACUATION_STALKER_FAR, 500.0f))
|
||||
me->GetMotionMaster()->MovePoint(POINT_STALKER_FAR, stalker->GetPosition(), true);
|
||||
break;
|
||||
case EVENT_DESPAWN:
|
||||
me->DespawnOrUnsummon();
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
private:
|
||||
EventMap _events;
|
||||
};
|
||||
|
||||
/*######
|
||||
## Quest 14154 - By the Skin of his Teeth
|
||||
######*/
|
||||
|
||||
enum WorgenRunt
|
||||
{
|
||||
EVENT_JUMP_TO_PRISON = 1,
|
||||
EVENT_AGGRO_PLAYER = 2,
|
||||
EVENT_FORCE_DESPAWN = 3,
|
||||
|
||||
PHASE_ROOF = 0,
|
||||
PHASE_COMBAT = 1,
|
||||
|
||||
WORGEN_ID_ROOF_1 = 0,
|
||||
WORGEN_ID_ROOF_2 = 1,
|
||||
WORGEN_ID_ROOF_3 = 2,
|
||||
WORGEN_ID_ROOF_4 = 3,
|
||||
WORGEN_ID_ROOF_5 = 4,
|
||||
WORGEN_ID_ROOF_6 = 5,
|
||||
WORGEN_ID_ROOF_7 = 6,
|
||||
|
||||
WORGEN_ID_CATHEDRAL_1 = 7,
|
||||
WORGEN_ID_CATHEDRAL_2 = 8,
|
||||
WORGEN_ID_CATHEDRAL_3 = 9,
|
||||
WORGEN_ID_CATHEDRAL_4 = 10,
|
||||
WORGEN_ID_CATHEDRAL_5 = 11,
|
||||
WORGEN_ID_CATHEDRAL_6 = 12,
|
||||
WORGEN_ID_CATHEDRAL_7 = 13,
|
||||
WORGEN_ID_CATHEDRAL_8 = 14,
|
||||
WORGEN_ID_CATHEDRAL_9 = 15,
|
||||
|
||||
NPC_WORGEN_RUNT_SPELL = 35188,
|
||||
};
|
||||
|
||||
static Position const RuntSpellSummonJumpPos = { -1671.915f, 1446.734f, 52.28712f };
|
||||
|
||||
struct npc_worgen_runt : public ScriptedAI
|
||||
{
|
||||
npc_worgen_runt(Creature* creature) : ScriptedAI(creature), _worgenID(0), _wayPointCounter(0), _jumped(false) { }
|
||||
|
||||
void JustEngagedWith(Unit* /*who*/) override
|
||||
{
|
||||
_events.SetPhase(PHASE_COMBAT);
|
||||
}
|
||||
|
||||
void IsSummonedBy(Unit* summoner) override
|
||||
{
|
||||
me->setActive(true); // we are in a phased and cut off map so we're fine to use that here
|
||||
_events.SetPhase(PHASE_ROOF);
|
||||
_events.ScheduleEvent(EVENT_FORCE_DESPAWN, 70s, 0, PHASE_ROOF);
|
||||
_playerGuid = summoner->GetGUID();
|
||||
if (me->GetEntry() == NPC_WORGEN_RUNT_SPELL)
|
||||
me->GetMotionMaster()->MoveJump(RuntSpellSummonJumpPos, 16.0f, 4.371286f);
|
||||
}
|
||||
|
||||
void DoAction(int32 action) override
|
||||
{
|
||||
_worgenID = action;
|
||||
switch (_worgenID) // ugly as fuck but needed for individual controls
|
||||
{
|
||||
// House roof cases
|
||||
case WORGEN_ID_ROOF_1:
|
||||
_wayPointCounter = runtHousePathSize1;
|
||||
me->GetMotionMaster()->MoveSmoothPath(runtHousePathSize1, worgenRuntHousePath1, runtHousePathSize1);
|
||||
break;
|
||||
case WORGEN_ID_ROOF_2:
|
||||
_wayPointCounter = runtHousePathSize2;
|
||||
me->GetMotionMaster()->MoveSmoothPath(runtHousePathSize2, worgenRuntHousePath2, runtHousePathSize2);
|
||||
break;
|
||||
case WORGEN_ID_ROOF_3:
|
||||
_wayPointCounter = runtHousePathSize3;
|
||||
me->GetMotionMaster()->MoveSmoothPath(runtHousePathSize3, worgenRuntHousePath3, runtHousePathSize3);
|
||||
break;
|
||||
case WORGEN_ID_ROOF_4:
|
||||
_wayPointCounter = runtHousePathSize4;
|
||||
me->GetMotionMaster()->MoveSmoothPath(runtHousePathSize4, worgenRuntHousePath4, runtHousePathSize4);
|
||||
break;
|
||||
case WORGEN_ID_ROOF_5:
|
||||
_wayPointCounter = runtHousePathSize5;
|
||||
me->GetMotionMaster()->MoveSmoothPath(runtHousePathSize5, worgenRuntHousePath5, runtHousePathSize5);
|
||||
break;
|
||||
case WORGEN_ID_ROOF_6:
|
||||
_wayPointCounter = runtHousePathSize6;
|
||||
me->GetMotionMaster()->MoveSmoothPath(runtHousePathSize6, worgenRuntHousePath6, runtHousePathSize6);
|
||||
break;
|
||||
case WORGEN_ID_ROOF_7:
|
||||
_wayPointCounter = runtHousePathSize7;
|
||||
me->GetMotionMaster()->MoveSmoothPath(runtHousePathSize7, worgenRuntHousePath7, runtHousePathSize7);
|
||||
break;
|
||||
// Cathedral cases
|
||||
case WORGEN_ID_CATHEDRAL_1:
|
||||
_wayPointCounter = runtCathedralPathSize1;
|
||||
me->GetMotionMaster()->MoveSmoothPath(runtCathedralPathSize1, worgenRuntCathedralPath1, runtCathedralPathSize1);
|
||||
break;
|
||||
case WORGEN_ID_CATHEDRAL_2:
|
||||
_wayPointCounter = runtCathedralPathSize2;
|
||||
me->GetMotionMaster()->MoveSmoothPath(runtCathedralPathSize2, worgenRuntCathedralPath1, runtCathedralPathSize2);
|
||||
break;
|
||||
case WORGEN_ID_CATHEDRAL_3:
|
||||
_wayPointCounter = runtCathedralPathSize3;
|
||||
me->GetMotionMaster()->MoveSmoothPath(runtCathedralPathSize3, worgenRuntCathedralPath1, runtCathedralPathSize3);
|
||||
break;
|
||||
case WORGEN_ID_CATHEDRAL_4:
|
||||
_wayPointCounter = runtCathedralPathSize4;
|
||||
me->GetMotionMaster()->MoveSmoothPath(runtCathedralPathSize4, worgenRuntCathedralPath1, runtCathedralPathSize4);
|
||||
break;
|
||||
case WORGEN_ID_CATHEDRAL_5:
|
||||
_wayPointCounter = runtCathedralPathSize5;
|
||||
me->GetMotionMaster()->MoveSmoothPath(runtCathedralPathSize5, worgenRuntCathedralPath1, runtCathedralPathSize5);
|
||||
break;
|
||||
case WORGEN_ID_CATHEDRAL_6:
|
||||
_wayPointCounter = runtCathedralPathSize6;
|
||||
me->GetMotionMaster()->MoveSmoothPath(runtCathedralPathSize6, worgenRuntCathedralPath1, runtCathedralPathSize6);
|
||||
break;
|
||||
case WORGEN_ID_CATHEDRAL_7:
|
||||
_wayPointCounter = runtCathedralPathSize7;
|
||||
me->GetMotionMaster()->MoveSmoothPath(runtCathedralPathSize7, worgenRuntCathedralPath1, runtCathedralPathSize7);
|
||||
break;
|
||||
case WORGEN_ID_CATHEDRAL_8:
|
||||
_wayPointCounter = runtCathedralPathSize8;
|
||||
me->GetMotionMaster()->MoveSmoothPath(runtCathedralPathSize8, worgenRuntCathedralPath1, runtCathedralPathSize8);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void JustDied(Unit* /*killer*/) override
|
||||
{
|
||||
me->DespawnOrUnsummon(5s);
|
||||
}
|
||||
|
||||
void MovementInform(uint32 type, uint32 pointId) override
|
||||
{
|
||||
if (type == EFFECT_MOTION_TYPE && pointId == _wayPointCounter && !_jumped)
|
||||
{
|
||||
_jumped = true;
|
||||
_events.ScheduleEvent(EVENT_JUMP_TO_PRISON, 1ms);
|
||||
}
|
||||
}
|
||||
|
||||
void UpdateAI(uint32 diff) override
|
||||
{
|
||||
if (!UpdateVictim() && !_events.IsInPhase(PHASE_ROOF))
|
||||
return;
|
||||
|
||||
_events.Update(diff);
|
||||
|
||||
while (uint32 eventId = _events.ExecuteEvent())
|
||||
{
|
||||
switch (eventId)
|
||||
{
|
||||
case EVENT_FORCE_DESPAWN:
|
||||
me->DespawnOrUnsummon();
|
||||
break;
|
||||
case EVENT_JUMP_TO_PRISON:
|
||||
me->GetMotionMaster()->MoveJump(worgenRuntJumpPos[_worgenID], 16.0f, _worgenID < WORGEN_ID_CATHEDRAL_1 ? 19.2911f : frand(3.945607f, 4.852813f));
|
||||
me->SetHomePosition(worgenRuntJumpPos[_worgenID]);
|
||||
_events.ScheduleEvent(EVENT_AGGRO_PLAYER, 2s);
|
||||
break;
|
||||
case EVENT_AGGRO_PLAYER:
|
||||
{
|
||||
Creature* crowley = me->FindNearestCreature(35077, 100.0f, true);
|
||||
Creature* tobias = me->FindNearestCreature(35124, 100.0f, true);
|
||||
|
||||
if (crowley && tobias)
|
||||
{
|
||||
if (me->GetDistance(crowley) <= me->GetDistance(tobias))
|
||||
AttackStart(crowley);
|
||||
else
|
||||
AttackStart(tobias);
|
||||
}
|
||||
else if (crowley)
|
||||
AttackStart(crowley);
|
||||
else if (tobias)
|
||||
AttackStart(tobias);
|
||||
|
||||
break;
|
||||
}
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
DoMeleeAttackIfReady();
|
||||
}
|
||||
private:
|
||||
uint32 _worgenID;
|
||||
uint32 _wayPointCounter;
|
||||
bool _jumped;
|
||||
ObjectGuid _playerGuid;
|
||||
EventMap _events;
|
||||
};
|
||||
|
||||
/*######
|
||||
## Quest 14159 - The Rebel Lord's Arsenal
|
||||
######*/
|
||||
|
||||
enum TheRebelLordsArsena
|
||||
{
|
||||
PHASE_ID_SUMMON = 170,
|
||||
PHASE_ID_WOUND = 171,
|
||||
NPC_LORNA_CROWLEY = 35378,
|
||||
NPC_GENERIC_TRIGGER_LAB = 35374,
|
||||
|
||||
SPELL_SHOOT_INSTAKILL = 67593,
|
||||
SPELL_COSMETIC_ATTACK = 42880,
|
||||
SPELL_PULL_TO = 67357,
|
||||
|
||||
EVENT_COSMETIC_ATTACK = 1,
|
||||
EVENT_JUMP_TO_PLAYER,
|
||||
EVENT_SHOOT_JOSIAH
|
||||
};
|
||||
|
||||
static Position const JosiahJumpPos = { -1796.63f, 1427.73f, 12.4624f };
|
||||
|
||||
struct npc_josiah_avery : public PassiveAI
|
||||
{
|
||||
npc_josiah_avery(Creature* creature) : PassiveAI(creature) { }
|
||||
|
||||
void IsSummonedBy(Unit* summoner) override
|
||||
{
|
||||
PhasingHandler::AddPhase(me, PHASE_ID_SUMMON, true);
|
||||
PhasingHandler::AddPhase(me, PHASE_ID_WOUND, true);
|
||||
_playerGuid = summoner->GetGUID();
|
||||
_events.ScheduleEvent(EVENT_COSMETIC_ATTACK, 500ms);
|
||||
}
|
||||
|
||||
void JustDied(Unit* /*killer*/) override
|
||||
{
|
||||
me->DespawnOrUnsummon(5s);
|
||||
}
|
||||
|
||||
void UpdateAI(uint32 diff) override
|
||||
{
|
||||
_events.Update(diff);
|
||||
|
||||
while (uint32 eventId = _events.ExecuteEvent())
|
||||
{
|
||||
switch (eventId)
|
||||
{
|
||||
case EVENT_COSMETIC_ATTACK:
|
||||
if (Player* player = ObjectAccessor::GetPlayer(*me, _playerGuid))
|
||||
{
|
||||
DoCast(player, SPELL_COSMETIC_ATTACK);
|
||||
if (Creature* lorna = me->FindNearestCreature(NPC_LORNA_CROWLEY, 30.0f, true))
|
||||
if (Creature* labTrigger = lorna->FindNearestCreature(NPC_GENERIC_TRIGGER_LAB, 5.0f, true))
|
||||
labTrigger->CastSpell(player, SPELL_PULL_TO);
|
||||
|
||||
_events.ScheduleEvent(EVENT_JUMP_TO_PLAYER, 1s);
|
||||
}
|
||||
break;
|
||||
case EVENT_JUMP_TO_PLAYER:
|
||||
me->GetMotionMaster()->MoveJump(JosiahJumpPos, 10.0f, 14.18636f);
|
||||
_events.ScheduleEvent(EVENT_SHOOT_JOSIAH, 500ms);
|
||||
break;
|
||||
case EVENT_SHOOT_JOSIAH:
|
||||
if (Creature* lorna = me->FindNearestCreature(NPC_LORNA_CROWLEY, 30.0f, true))
|
||||
lorna->CastSpell(me, SPELL_SHOOT_INSTAKILL, true);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
private:
|
||||
ObjectGuid _playerGuid;
|
||||
EventMap _events;
|
||||
};
|
||||
|
||||
/*######
|
||||
## Quest 14293 - Save Krennan Aranas
|
||||
######*/
|
||||
|
||||
static Position const GreymanesHorseJumpPos = { -1676.16f, 1346.19f, 15.1349f };
|
||||
|
||||
enum GreymanesHorse
|
||||
{
|
||||
SAY_ANNOUNCE_RESCUE = 0,
|
||||
SAY_RESCUED = 0,
|
||||
SAY_TRAPPED = 0,
|
||||
|
||||
PATH_KRENNAN_TREE = 0,
|
||||
PATH_KRENNAN_BACK = 1,
|
||||
|
||||
EVENT_START_PATH_1 = 1,
|
||||
EVENT_START_PATH_2,
|
||||
EVENT_JUMP_TO_KRENNAN,
|
||||
EVENT_ANNOUNCE_RESCUE,
|
||||
EVENT_DISMOUNT_PLAYER,
|
||||
|
||||
NPC_RESCUED_KRENNAN = 35907,
|
||||
NPC_TRAPPED_KRENNAN = 35753,
|
||||
};
|
||||
|
||||
struct npc_greymanes_horse : public VehicleAI
|
||||
{
|
||||
npc_greymanes_horse(Creature* creature) : VehicleAI(creature), _currentPath(PATH_KRENNAN_TREE) { }
|
||||
|
||||
void DamageTaken(Unit* /*attacker*/, uint32& damage) override
|
||||
{
|
||||
if (me->GetHealth() <= 1)
|
||||
damage = 0;
|
||||
else if (damage >= me->GetHealth())
|
||||
damage = me->GetHealth() - 1;
|
||||
}
|
||||
|
||||
void PassengerBoarded(Unit* passenger, int8 /*seatId*/, bool apply) override
|
||||
{
|
||||
if (apply && passenger->GetTypeId() == TYPEID_PLAYER)
|
||||
{
|
||||
me->SetControlled(true, UNIT_STATE_ROOT);
|
||||
_events.ScheduleEvent(EVENT_START_PATH_1, 1s);
|
||||
}
|
||||
else if (apply && passenger->GetEntry() == NPC_RESCUED_KRENNAN)
|
||||
_events.ScheduleEvent(EVENT_START_PATH_2, 1s);
|
||||
}
|
||||
|
||||
void MovementInform(uint32 type, uint32 pointId) override
|
||||
{
|
||||
if (type == EFFECT_MOTION_TYPE && pointId == pathSize1 && _currentPath == PATH_KRENNAN_TREE)
|
||||
_events.ScheduleEvent(EVENT_JUMP_TO_KRENNAN, 1ms);
|
||||
else if (type == EFFECT_MOTION_TYPE && pointId == pathSize2 && _currentPath == PATH_KRENNAN_BACK)
|
||||
_events.ScheduleEvent(EVENT_DISMOUNT_PLAYER, 1ms);
|
||||
}
|
||||
|
||||
void UpdateAI(uint32 diff) override
|
||||
{
|
||||
_events.Update(diff);
|
||||
|
||||
if (me->HasUnitState(UNIT_STATE_CASTING))
|
||||
return;
|
||||
|
||||
while (uint32 eventId = _events.ExecuteEvent())
|
||||
{
|
||||
switch (eventId)
|
||||
{
|
||||
case EVENT_START_PATH_1:
|
||||
me->SetControlled(false, UNIT_STATE_ROOT);
|
||||
_currentPath = PATH_KRENNAN_TREE;
|
||||
me->GetMotionMaster()->MoveSmoothPath(pathSize1, greymanesHorsePath1, pathSize1);
|
||||
break;
|
||||
case EVENT_JUMP_TO_KRENNAN:
|
||||
me->GetMotionMaster()->MoveJump(GreymanesHorseJumpPos, 16.0f, 14.00968f);
|
||||
_events.ScheduleEvent(EVENT_ANNOUNCE_RESCUE, 2s);
|
||||
break;
|
||||
case EVENT_ANNOUNCE_RESCUE:
|
||||
me->SetControlled(true, UNIT_STATE_ROOT);
|
||||
if (Unit* passenger = me->GetVehicleKit()->GetPassenger(0))
|
||||
{
|
||||
Talk(SAY_ANNOUNCE_RESCUE, passenger);
|
||||
if (Creature* krennan = me->FindNearestCreature(NPC_TRAPPED_KRENNAN, 30.0f, true))
|
||||
krennan->AI()->Talk(SAY_TRAPPED, passenger);
|
||||
}
|
||||
break;
|
||||
case EVENT_START_PATH_2:
|
||||
me->SetControlled(true, UNIT_STATE_ROOT);
|
||||
_currentPath = PATH_KRENNAN_BACK;
|
||||
me->GetMotionMaster()->MoveSmoothPath(pathSize2, greymanesHorsePath2, pathSize2);
|
||||
break;
|
||||
case EVENT_DISMOUNT_PLAYER:
|
||||
if (Unit* passenger = me->GetVehicleKit()->GetPassenger(1))
|
||||
{
|
||||
if (Unit* player = me->GetVehicleKit()->GetPassenger(0))
|
||||
{
|
||||
if (Creature* krennan = passenger->ToCreature())
|
||||
{
|
||||
std::vector<Unit*> storedAttackers;
|
||||
|
||||
for (Unit* attacker : me->getAttackers())
|
||||
storedAttackers.push_back(attacker);
|
||||
|
||||
for (Unit* attacker : storedAttackers)
|
||||
{
|
||||
if (Creature* creature = attacker->ToCreature())
|
||||
if (creature->IsAIEnabled())
|
||||
creature->AI()->EnterEvadeMode();
|
||||
}
|
||||
|
||||
player->ExitVehicle();
|
||||
krennan->AddUnitState(UNIT_STATE_ROOT);
|
||||
krennan->ExitVehicle();
|
||||
krennan->AI()->Talk(SAY_RESCUED, me);
|
||||
krennan->DespawnOrUnsummon(7s);
|
||||
}
|
||||
}
|
||||
}
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
private:
|
||||
EventMap _events;
|
||||
uint32 _currentPath;
|
||||
};
|
||||
|
||||
/*######
|
||||
## Quest 14212 - Sacrifices
|
||||
######*/
|
||||
|
||||
enum CrowleysHorse
|
||||
{
|
||||
EVENT_JUMP_OVER_BARRICADES_1 = 1,
|
||||
EVENT_JUMP_OVER_BARRICADES_2,
|
||||
EVENT_MOVE_PATH_MAIN_1,
|
||||
EVENT_MOVE_PATH_MAIN_2,
|
||||
EVENT_MOVE_OFF_PATH = 6,
|
||||
|
||||
PATH_ID_CROWLEYS_HORSE_1 = 352310,
|
||||
PATH_ID_CROWLEYS_HORSE_2 = 352311,
|
||||
PATH_ID_CROWLEYS_HORSE_3 = 444280,
|
||||
|
||||
SPELL_THROW_TORCH = 67063,
|
||||
NPC_CROWLEYS_HORSE_2 = 44428,
|
||||
};
|
||||
|
||||
static Position const CrowleysHorseJumpPos = { -1714.762f, 1673.16f, 20.49182f };
|
||||
static Position const CrowleysHorseJumpPos2 = { -1566.71f, 1708.04f, 20.4849f };
|
||||
|
||||
struct npc_crowleys_horse : public VehicleAI
|
||||
{
|
||||
npc_crowleys_horse(Creature* creature) : VehicleAI(creature), _currentPath(0) { }
|
||||
|
||||
void PassengerBoarded(Unit* passenger, int8 /*seatId*/, bool apply) override
|
||||
{
|
||||
if (apply && passenger->GetTypeId() == TYPEID_PLAYER && me->GetEntry() != NPC_CROWLEYS_HORSE_2)
|
||||
{
|
||||
me->SetControlled(true, UNIT_STATE_ROOT);
|
||||
_events.ScheduleEvent(EVENT_JUMP_OVER_BARRICADES_1, 2s);
|
||||
}
|
||||
else if (apply && passenger->GetTypeId() == TYPEID_PLAYER)
|
||||
{
|
||||
me->SetControlled(true, UNIT_STATE_ROOT);
|
||||
_events.ScheduleEvent(EVENT_MOVE_OFF_PATH, 2s);
|
||||
}
|
||||
}
|
||||
|
||||
void MovementInform(uint32 type, uint32 pointId) override
|
||||
{
|
||||
if (type == WAYPOINT_MOTION_TYPE && pointId == 14 && _currentPath == PATH_ID_CROWLEYS_HORSE_1)
|
||||
_events.ScheduleEvent(EVENT_JUMP_OVER_BARRICADES_2, 1s);
|
||||
else if (type == WAYPOINT_MOTION_TYPE && pointId == 15 && _currentPath == PATH_ID_CROWLEYS_HORSE_2)
|
||||
_events.ScheduleEvent(EVENT_DISMOUNT_PLAYER, 1ms);
|
||||
else if (type == WAYPOINT_MOTION_TYPE && pointId == 16 && _currentPath == PATH_ID_CROWLEYS_HORSE_3)
|
||||
_events.ScheduleEvent(EVENT_DISMOUNT_PLAYER, 1ms);
|
||||
}
|
||||
|
||||
void UpdateAI(uint32 diff) override
|
||||
{
|
||||
_events.Update(diff);
|
||||
|
||||
while (uint32 eventId = _events.ExecuteEvent())
|
||||
{
|
||||
switch (eventId)
|
||||
{
|
||||
case EVENT_JUMP_OVER_BARRICADES_1:
|
||||
me->SetControlled(false, UNIT_STATE_ROOT);
|
||||
me->GetMotionMaster()->MoveJump(CrowleysHorseJumpPos, 16.0f, 18.56182f);
|
||||
_events.ScheduleEvent(EVENT_MOVE_PATH_MAIN_1, 2s);
|
||||
break;
|
||||
case EVENT_MOVE_PATH_MAIN_1:
|
||||
me->GetMotionMaster()->MovePath(PATH_ID_CROWLEYS_HORSE_1, false);
|
||||
_currentPath = PATH_ID_CROWLEYS_HORSE_1;
|
||||
break;
|
||||
case EVENT_JUMP_OVER_BARRICADES_2:
|
||||
me->GetMotionMaster()->MoveJump(CrowleysHorseJumpPos2, 16.0f, 18.56182f);
|
||||
_events.ScheduleEvent(EVENT_MOVE_PATH_MAIN_2, 2s);
|
||||
break;
|
||||
case EVENT_MOVE_PATH_MAIN_2:
|
||||
_currentPath = PATH_ID_CROWLEYS_HORSE_2;
|
||||
me->GetMotionMaster()->MovePath(PATH_ID_CROWLEYS_HORSE_2, false);
|
||||
break;
|
||||
case EVENT_DISMOUNT_PLAYER:
|
||||
{
|
||||
std::set<Unit*> attackersCopy = me->getAttackers();
|
||||
for (Unit* attacker : attackersCopy)
|
||||
{
|
||||
if (Creature* creature = attacker->ToCreature())
|
||||
if (creature->IsAIEnabled())
|
||||
creature->AI()->EnterEvadeMode();
|
||||
}
|
||||
|
||||
me->RemoveAurasDueToSpell(VEHICLE_SPELL_RIDE_HARDCODED);
|
||||
me->DespawnOrUnsummon(5s);
|
||||
break;
|
||||
}
|
||||
case EVENT_MOVE_OFF_PATH:
|
||||
me->SetControlled(false, UNIT_STATE_ROOT);
|
||||
_currentPath = PATH_ID_CROWLEYS_HORSE_3;
|
||||
me->GetMotionMaster()->MovePath(PATH_ID_CROWLEYS_HORSE_3, false);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
private:
|
||||
EventMap _events;
|
||||
uint32 _currentPath;
|
||||
};
|
||||
|
||||
enum GileanCrow
|
||||
{
|
||||
SPELL_GILNEAN_CROW = 93275,
|
||||
EVENT_APPLY_HOVER_BYTES = 1,
|
||||
EVENT_FLY_AWAY_1 = 2,
|
||||
EVENT_FLY_AWAY_2 = 3,
|
||||
|
||||
POINT_NONE = 0,
|
||||
POINT_CROW_FLIGHT = 1
|
||||
};
|
||||
|
||||
struct npc_gilnean_crow : public PassiveAI
|
||||
{
|
||||
npc_gilnean_crow(Creature* creature) : PassiveAI(creature) { }
|
||||
|
||||
void SpellHit(WorldObject* /*caster*/, SpellInfo const* spell) override
|
||||
{
|
||||
if (spell->Id == SPELL_GILNEAN_CROW)
|
||||
_events.ScheduleEvent(EVENT_APPLY_HOVER_BYTES, 500ms);
|
||||
}
|
||||
|
||||
void MovementInform(uint32 type, uint32 pointId) override
|
||||
{
|
||||
if (type == POINT_MOTION_TYPE && pointId == POINT_CROW_FLIGHT)
|
||||
{
|
||||
Position pos = me->GetRandomNearPosition(8.0f);
|
||||
pos.m_positionZ = me->GetPositionZ() + 10.0f;
|
||||
me->GetMotionMaster()->MovePoint(POINT_NONE, pos);
|
||||
me->DespawnOrUnsummon(14s);
|
||||
}
|
||||
}
|
||||
|
||||
void UpdateAI(uint32 diff) override
|
||||
{
|
||||
_events.Update(diff);
|
||||
|
||||
while (uint32 eventId = _events.ExecuteEvent())
|
||||
{
|
||||
switch (eventId)
|
||||
{
|
||||
case EVENT_APPLY_HOVER_BYTES:
|
||||
me->SetStandState(UNIT_STAND_STATE_SIT);
|
||||
me->SetAnimTier(AnimTier::Submerged);
|
||||
_events.ScheduleEvent(EVENT_FLY_AWAY_1, 1s);
|
||||
break;
|
||||
case EVENT_FLY_AWAY_1:
|
||||
{
|
||||
Position pos = me->GetRandomNearPosition(7.0f);
|
||||
pos.m_positionZ = me->GetPositionZ() + 8.0f;
|
||||
me->GetMotionMaster()->MovePoint(POINT_CROW_FLIGHT, pos);
|
||||
break;
|
||||
}
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
private:
|
||||
EventMap _events;
|
||||
};
|
||||
}
|
||||
|
||||
void AddSC_gilneas_chapter_1()
|
||||
{
|
||||
using namespace Gilneas::Chapter1;
|
||||
RegisterCreatureAI(npc_frightened_citizen);
|
||||
RegisterCreatureAI(npc_worgen_runt);
|
||||
RegisterCreatureAI(npc_josiah_avery);
|
||||
RegisterCreatureAI(npc_greymanes_horse);
|
||||
RegisterCreatureAI(npc_crowleys_horse);
|
||||
RegisterCreatureAI(npc_gilnean_crow);
|
||||
}
|
||||
+727
@@ -0,0 +1,727 @@
|
||||
/*
|
||||
* This file is part of the TrinityCore 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 "gilneas.h"
|
||||
#include "ScriptMgr.h"
|
||||
#include "CombatAI.h"
|
||||
#include "MotionMaster.h"
|
||||
#include "ObjectAccessor.h"
|
||||
#include "Player.h"
|
||||
#include "PassiveAI.h"
|
||||
#include "PhasingHandler.h"
|
||||
#include "ScriptedCreature.h"
|
||||
#include "SpellInfo.h"
|
||||
#include "SpellScript.h"
|
||||
#include "TemporarySummon.h"
|
||||
#include "Vehicle.h"
|
||||
|
||||
namespace Gilneas::Chapter1
|
||||
{
|
||||
|
||||
/*######
|
||||
## Quest 14098 - Evacuate the Merchant Square
|
||||
######*/
|
||||
|
||||
enum FrightenedCitizen
|
||||
{
|
||||
SAY_FRIGHTENED_CITIZEN_RESCUE = 0,
|
||||
|
||||
NPC_EVACUATION_STALKER_FIRST = 35830,
|
||||
NPC_EVACUATION_STALKER_NEAR = 35010,
|
||||
NPC_EVACUATION_STALKER_FAR = 35011,
|
||||
NPC_RAMPAGING_WORGEN = 35660,
|
||||
|
||||
CREDIT_35830 = 35830,
|
||||
|
||||
POINT_STALKER_FIRST = 1,
|
||||
POINT_STALKER_NEAR = 2,
|
||||
POINT_STALKER_FAR = 3,
|
||||
|
||||
EVENT_TALK_FRIGHTENED = 1,
|
||||
EVENT_MOVE_TO_NEAR_STALKER = 2,
|
||||
EVENT_MOVE_TO_FAR_STALKER = 3,
|
||||
EVENT_DESPAWN = 4
|
||||
};
|
||||
|
||||
struct npc_frightened_citizen : public PassiveAI
|
||||
{
|
||||
npc_frightened_citizen(Creature* creature) : PassiveAI(creature) { }
|
||||
|
||||
void IsSummonedBy(Unit* /*summoner*/) override
|
||||
{
|
||||
if (Creature* stalkerNear = me->FindNearestCreature(NPC_EVACUATION_STALKER_FIRST, 20.0f))
|
||||
me->GetMotionMaster()->MovePoint(POINT_STALKER_FIRST, stalkerNear->GetPosition(), true);
|
||||
}
|
||||
|
||||
void MovementInform(uint32 type, uint32 id) override
|
||||
{
|
||||
if (type != POINT_MOTION_TYPE)
|
||||
return;
|
||||
|
||||
switch (id)
|
||||
{
|
||||
case POINT_STALKER_FIRST:
|
||||
_events.ScheduleEvent(EVENT_TALK_FRIGHTENED, 1s);
|
||||
break;
|
||||
case POINT_STALKER_NEAR:
|
||||
_events.ScheduleEvent(EVENT_MOVE_TO_FAR_STALKER, 1ms);
|
||||
break;
|
||||
case POINT_STALKER_FAR:
|
||||
_events.ScheduleEvent(EVENT_DESPAWN, 1ms);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void UpdateAI(uint32 diff) override
|
||||
{
|
||||
_events.Update(diff);
|
||||
|
||||
while (uint32 eventId = _events.ExecuteEvent())
|
||||
{
|
||||
switch (eventId)
|
||||
{
|
||||
case EVENT_TALK_FRIGHTENED:
|
||||
if (Unit* summoner = me->ToTempSummon()->GetSummoner())
|
||||
{
|
||||
if (Player* player = summoner->ToPlayer())
|
||||
{
|
||||
player->KilledMonsterCredit(CREDIT_35830);
|
||||
Talk(SAY_FRIGHTENED_CITIZEN_RESCUE, summoner);
|
||||
}
|
||||
}
|
||||
_events.ScheduleEvent(EVENT_MOVE_TO_NEAR_STALKER, 2s);
|
||||
break;
|
||||
case EVENT_MOVE_TO_NEAR_STALKER:
|
||||
if (Creature* stalker = me->FindNearestCreature(NPC_EVACUATION_STALKER_FAR, 50.0f))
|
||||
me->GetMotionMaster()->MovePoint(POINT_STALKER_FAR, stalker->GetPosition(), true);
|
||||
else if (Creature* stalker = me->FindNearestCreature(NPC_EVACUATION_STALKER_NEAR, 100.0f))
|
||||
me->GetMotionMaster()->MovePoint(POINT_STALKER_NEAR, stalker->GetPosition(), true);
|
||||
break;
|
||||
case EVENT_MOVE_TO_FAR_STALKER:
|
||||
if (Creature* stalker = me->FindNearestCreature(NPC_EVACUATION_STALKER_FAR, 500.0f))
|
||||
me->GetMotionMaster()->MovePoint(POINT_STALKER_FAR, stalker->GetPosition(), true);
|
||||
break;
|
||||
case EVENT_DESPAWN:
|
||||
me->DespawnOrUnsummon();
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
private:
|
||||
EventMap _events;
|
||||
};
|
||||
|
||||
/*######
|
||||
## Quest 14154 - By the Skin of his Teeth
|
||||
######*/
|
||||
|
||||
enum WorgenRunt
|
||||
{
|
||||
EVENT_JUMP_TO_PRISON = 1,
|
||||
EVENT_AGGRO_PLAYER = 2,
|
||||
EVENT_FORCE_DESPAWN = 3,
|
||||
|
||||
PHASE_ROOF = 0,
|
||||
PHASE_COMBAT = 1,
|
||||
|
||||
WORGEN_ID_ROOF_1 = 0,
|
||||
WORGEN_ID_ROOF_2 = 1,
|
||||
WORGEN_ID_ROOF_3 = 2,
|
||||
WORGEN_ID_ROOF_4 = 3,
|
||||
WORGEN_ID_ROOF_5 = 4,
|
||||
WORGEN_ID_ROOF_6 = 5,
|
||||
WORGEN_ID_ROOF_7 = 6,
|
||||
|
||||
WORGEN_ID_CATHEDRAL_1 = 7,
|
||||
WORGEN_ID_CATHEDRAL_2 = 8,
|
||||
WORGEN_ID_CATHEDRAL_3 = 9,
|
||||
WORGEN_ID_CATHEDRAL_4 = 10,
|
||||
WORGEN_ID_CATHEDRAL_5 = 11,
|
||||
WORGEN_ID_CATHEDRAL_6 = 12,
|
||||
WORGEN_ID_CATHEDRAL_7 = 13,
|
||||
WORGEN_ID_CATHEDRAL_8 = 14,
|
||||
WORGEN_ID_CATHEDRAL_9 = 15,
|
||||
|
||||
NPC_WORGEN_RUNT_SPELL = 35188,
|
||||
};
|
||||
|
||||
static Position const RuntSpellSummonJumpPos = { -1671.915f, 1446.734f, 52.28712f };
|
||||
|
||||
struct npc_worgen_runt : public ScriptedAI
|
||||
{
|
||||
npc_worgen_runt(Creature* creature) : ScriptedAI(creature), _worgenID(0), _wayPointCounter(0), _jumped(false) { }
|
||||
|
||||
void JustEngagedWith(Unit* /*who*/) override
|
||||
{
|
||||
_events.SetPhase(PHASE_COMBAT);
|
||||
}
|
||||
|
||||
void IsSummonedBy(Unit* summoner) override
|
||||
{
|
||||
me->setActive(true); // we are in a phased and cut off map so we're fine to use that here
|
||||
_events.SetPhase(PHASE_ROOF);
|
||||
_events.ScheduleEvent(EVENT_FORCE_DESPAWN, 70s, 0, PHASE_ROOF);
|
||||
_playerGuid = summoner->GetGUID();
|
||||
if (me->GetEntry() == NPC_WORGEN_RUNT_SPELL)
|
||||
me->GetMotionMaster()->MoveJump(RuntSpellSummonJumpPos, 16.0f, 4.371286f);
|
||||
}
|
||||
|
||||
void DoAction(int32 action) override
|
||||
{
|
||||
_worgenID = action;
|
||||
switch (_worgenID) // ugly as fuck but needed for individual controls
|
||||
{
|
||||
// House roof cases
|
||||
case WORGEN_ID_ROOF_1:
|
||||
_wayPointCounter = runtHousePathSize1;
|
||||
me->GetMotionMaster()->MoveSmoothPath(runtHousePathSize1, worgenRuntHousePath1, runtHousePathSize1);
|
||||
break;
|
||||
case WORGEN_ID_ROOF_2:
|
||||
_wayPointCounter = runtHousePathSize2;
|
||||
me->GetMotionMaster()->MoveSmoothPath(runtHousePathSize2, worgenRuntHousePath2, runtHousePathSize2);
|
||||
break;
|
||||
case WORGEN_ID_ROOF_3:
|
||||
_wayPointCounter = runtHousePathSize3;
|
||||
me->GetMotionMaster()->MoveSmoothPath(runtHousePathSize3, worgenRuntHousePath3, runtHousePathSize3);
|
||||
break;
|
||||
case WORGEN_ID_ROOF_4:
|
||||
_wayPointCounter = runtHousePathSize4;
|
||||
me->GetMotionMaster()->MoveSmoothPath(runtHousePathSize4, worgenRuntHousePath4, runtHousePathSize4);
|
||||
break;
|
||||
case WORGEN_ID_ROOF_5:
|
||||
_wayPointCounter = runtHousePathSize5;
|
||||
me->GetMotionMaster()->MoveSmoothPath(runtHousePathSize5, worgenRuntHousePath5, runtHousePathSize5);
|
||||
break;
|
||||
case WORGEN_ID_ROOF_6:
|
||||
_wayPointCounter = runtHousePathSize6;
|
||||
me->GetMotionMaster()->MoveSmoothPath(runtHousePathSize6, worgenRuntHousePath6, runtHousePathSize6);
|
||||
break;
|
||||
case WORGEN_ID_ROOF_7:
|
||||
_wayPointCounter = runtHousePathSize7;
|
||||
me->GetMotionMaster()->MoveSmoothPath(runtHousePathSize7, worgenRuntHousePath7, runtHousePathSize7);
|
||||
break;
|
||||
// Cathedral cases
|
||||
case WORGEN_ID_CATHEDRAL_1:
|
||||
_wayPointCounter = runtCathedralPathSize1;
|
||||
me->GetMotionMaster()->MoveSmoothPath(runtCathedralPathSize1, worgenRuntCathedralPath1, runtCathedralPathSize1);
|
||||
break;
|
||||
case WORGEN_ID_CATHEDRAL_2:
|
||||
_wayPointCounter = runtCathedralPathSize2;
|
||||
me->GetMotionMaster()->MoveSmoothPath(runtCathedralPathSize2, worgenRuntCathedralPath1, runtCathedralPathSize2);
|
||||
break;
|
||||
case WORGEN_ID_CATHEDRAL_3:
|
||||
_wayPointCounter = runtCathedralPathSize3;
|
||||
me->GetMotionMaster()->MoveSmoothPath(runtCathedralPathSize3, worgenRuntCathedralPath1, runtCathedralPathSize3);
|
||||
break;
|
||||
case WORGEN_ID_CATHEDRAL_4:
|
||||
_wayPointCounter = runtCathedralPathSize4;
|
||||
me->GetMotionMaster()->MoveSmoothPath(runtCathedralPathSize4, worgenRuntCathedralPath1, runtCathedralPathSize4);
|
||||
break;
|
||||
case WORGEN_ID_CATHEDRAL_5:
|
||||
_wayPointCounter = runtCathedralPathSize5;
|
||||
me->GetMotionMaster()->MoveSmoothPath(runtCathedralPathSize5, worgenRuntCathedralPath1, runtCathedralPathSize5);
|
||||
break;
|
||||
case WORGEN_ID_CATHEDRAL_6:
|
||||
_wayPointCounter = runtCathedralPathSize6;
|
||||
me->GetMotionMaster()->MoveSmoothPath(runtCathedralPathSize6, worgenRuntCathedralPath1, runtCathedralPathSize6);
|
||||
break;
|
||||
case WORGEN_ID_CATHEDRAL_7:
|
||||
_wayPointCounter = runtCathedralPathSize7;
|
||||
me->GetMotionMaster()->MoveSmoothPath(runtCathedralPathSize7, worgenRuntCathedralPath1, runtCathedralPathSize7);
|
||||
break;
|
||||
case WORGEN_ID_CATHEDRAL_8:
|
||||
_wayPointCounter = runtCathedralPathSize8;
|
||||
me->GetMotionMaster()->MoveSmoothPath(runtCathedralPathSize8, worgenRuntCathedralPath1, runtCathedralPathSize8);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void JustDied(Unit* /*killer*/) override
|
||||
{
|
||||
me->DespawnOrUnsummon(5s);
|
||||
}
|
||||
|
||||
void MovementInform(uint32 type, uint32 pointId) override
|
||||
{
|
||||
if (type == EFFECT_MOTION_TYPE && pointId == _wayPointCounter && !_jumped)
|
||||
{
|
||||
_jumped = true;
|
||||
_events.ScheduleEvent(EVENT_JUMP_TO_PRISON, 1ms);
|
||||
}
|
||||
}
|
||||
|
||||
void UpdateAI(uint32 diff) override
|
||||
{
|
||||
if (!UpdateVictim() && !_events.IsInPhase(PHASE_ROOF))
|
||||
return;
|
||||
|
||||
_events.Update(diff);
|
||||
|
||||
while (uint32 eventId = _events.ExecuteEvent())
|
||||
{
|
||||
switch (eventId)
|
||||
{
|
||||
case EVENT_FORCE_DESPAWN:
|
||||
me->DespawnOrUnsummon();
|
||||
break;
|
||||
case EVENT_JUMP_TO_PRISON:
|
||||
me->GetMotionMaster()->MoveJump(worgenRuntJumpPos[_worgenID], 16.0f, _worgenID < WORGEN_ID_CATHEDRAL_1 ? 19.2911f : frand(3.945607f, 4.852813f));
|
||||
me->SetHomePosition(worgenRuntJumpPos[_worgenID]);
|
||||
_events.ScheduleEvent(EVENT_AGGRO_PLAYER, 2s);
|
||||
break;
|
||||
case EVENT_AGGRO_PLAYER:
|
||||
{
|
||||
Creature* crowley = me->FindNearestCreature(35077, 100.0f, true);
|
||||
Creature* tobias = me->FindNearestCreature(35124, 100.0f, true);
|
||||
|
||||
if (crowley && tobias)
|
||||
{
|
||||
if (me->GetDistance(crowley) <= me->GetDistance(tobias))
|
||||
AttackStart(crowley);
|
||||
else
|
||||
AttackStart(tobias);
|
||||
}
|
||||
else if (crowley)
|
||||
AttackStart(crowley);
|
||||
else if (tobias)
|
||||
AttackStart(tobias);
|
||||
|
||||
break;
|
||||
}
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
DoMeleeAttackIfReady();
|
||||
}
|
||||
private:
|
||||
uint32 _worgenID;
|
||||
uint32 _wayPointCounter;
|
||||
bool _jumped;
|
||||
ObjectGuid _playerGuid;
|
||||
EventMap _events;
|
||||
};
|
||||
|
||||
/*######
|
||||
## Quest 14159 - The Rebel Lord's Arsenal
|
||||
######*/
|
||||
|
||||
enum TheRebelLordsArsena
|
||||
{
|
||||
PHASE_ID_SUMMON = 170,
|
||||
PHASE_ID_WOUND = 171,
|
||||
NPC_LORNA_CROWLEY = 35378,
|
||||
NPC_GENERIC_TRIGGER_LAB = 35374,
|
||||
|
||||
SPELL_SHOOT_INSTAKILL = 67593,
|
||||
SPELL_COSMETIC_ATTACK = 42880,
|
||||
SPELL_PULL_TO = 67357,
|
||||
|
||||
EVENT_COSMETIC_ATTACK = 1,
|
||||
EVENT_JUMP_TO_PLAYER,
|
||||
EVENT_SHOOT_JOSIAH
|
||||
};
|
||||
|
||||
static Position const JosiahJumpPos = { -1796.63f, 1427.73f, 12.4624f };
|
||||
|
||||
struct npc_josiah_avery : public PassiveAI
|
||||
{
|
||||
npc_josiah_avery(Creature* creature) : PassiveAI(creature) { }
|
||||
|
||||
void IsSummonedBy(Unit* summoner) override
|
||||
{
|
||||
PhasingHandler::AddPhase(me, PHASE_ID_SUMMON, true);
|
||||
PhasingHandler::AddPhase(me, PHASE_ID_WOUND, true);
|
||||
_playerGuid = summoner->GetGUID();
|
||||
_events.ScheduleEvent(EVENT_COSMETIC_ATTACK, 500ms);
|
||||
}
|
||||
|
||||
void JustDied(Unit* /*killer*/) override
|
||||
{
|
||||
me->DespawnOrUnsummon(5s);
|
||||
}
|
||||
|
||||
void UpdateAI(uint32 diff) override
|
||||
{
|
||||
_events.Update(diff);
|
||||
|
||||
while (uint32 eventId = _events.ExecuteEvent())
|
||||
{
|
||||
switch (eventId)
|
||||
{
|
||||
case EVENT_COSMETIC_ATTACK:
|
||||
if (Player* player = ObjectAccessor::GetPlayer(*me, _playerGuid))
|
||||
{
|
||||
DoCast(player, SPELL_COSMETIC_ATTACK);
|
||||
if (Creature* lorna = me->FindNearestCreature(NPC_LORNA_CROWLEY, 30.0f, true))
|
||||
if (Creature* labTrigger = lorna->FindNearestCreature(NPC_GENERIC_TRIGGER_LAB, 5.0f, true))
|
||||
labTrigger->CastSpell(player, SPELL_PULL_TO);
|
||||
|
||||
_events.ScheduleEvent(EVENT_JUMP_TO_PLAYER, 1s);
|
||||
}
|
||||
break;
|
||||
case EVENT_JUMP_TO_PLAYER:
|
||||
me->GetMotionMaster()->MoveJump(JosiahJumpPos, 10.0f, 14.18636f);
|
||||
_events.ScheduleEvent(EVENT_SHOOT_JOSIAH, 500ms);
|
||||
break;
|
||||
case EVENT_SHOOT_JOSIAH:
|
||||
if (Creature* lorna = me->FindNearestCreature(NPC_LORNA_CROWLEY, 30.0f, true))
|
||||
lorna->CastSpell(me, SPELL_SHOOT_INSTAKILL, true);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
private:
|
||||
ObjectGuid _playerGuid;
|
||||
EventMap _events;
|
||||
};
|
||||
|
||||
/*######
|
||||
## Quest 14293 - Save Krennan Aranas
|
||||
######*/
|
||||
|
||||
static Position const GreymanesHorseJumpPos = { -1676.16f, 1346.19f, 15.1349f };
|
||||
|
||||
enum GreymanesHorse
|
||||
{
|
||||
SAY_ANNOUNCE_RESCUE = 0,
|
||||
SAY_RESCUED = 0,
|
||||
SAY_TRAPPED = 0,
|
||||
|
||||
PATH_KRENNAN_TREE = 0,
|
||||
PATH_KRENNAN_BACK = 1,
|
||||
|
||||
EVENT_START_PATH_1 = 1,
|
||||
EVENT_START_PATH_2,
|
||||
EVENT_JUMP_TO_KRENNAN,
|
||||
EVENT_ANNOUNCE_RESCUE,
|
||||
EVENT_DISMOUNT_PLAYER,
|
||||
|
||||
NPC_RESCUED_KRENNAN = 35907,
|
||||
NPC_TRAPPED_KRENNAN = 35753,
|
||||
};
|
||||
|
||||
struct npc_greymanes_horse : public VehicleAI
|
||||
{
|
||||
npc_greymanes_horse(Creature* creature) : VehicleAI(creature), _currentPath(PATH_KRENNAN_TREE) { }
|
||||
|
||||
void DamageTaken(Unit* /*attacker*/, uint32& damage) override
|
||||
{
|
||||
if (me->GetHealth() <= 1)
|
||||
damage = 0;
|
||||
else if (damage >= me->GetHealth())
|
||||
damage = me->GetHealth() - 1;
|
||||
}
|
||||
|
||||
void PassengerBoarded(Unit* passenger, int8 /*seatId*/, bool apply) override
|
||||
{
|
||||
if (apply && passenger->GetTypeId() == TYPEID_PLAYER)
|
||||
{
|
||||
me->SetControlled(true, UNIT_STATE_ROOT);
|
||||
_events.ScheduleEvent(EVENT_START_PATH_1, 1s);
|
||||
}
|
||||
else if (apply && passenger->GetEntry() == NPC_RESCUED_KRENNAN)
|
||||
_events.ScheduleEvent(EVENT_START_PATH_2, 1s);
|
||||
}
|
||||
|
||||
void MovementInform(uint32 type, uint32 pointId) override
|
||||
{
|
||||
if (type == EFFECT_MOTION_TYPE && pointId == pathSize1 && _currentPath == PATH_KRENNAN_TREE)
|
||||
_events.ScheduleEvent(EVENT_JUMP_TO_KRENNAN, 1ms);
|
||||
else if (type == EFFECT_MOTION_TYPE && pointId == pathSize2 && _currentPath == PATH_KRENNAN_BACK)
|
||||
_events.ScheduleEvent(EVENT_DISMOUNT_PLAYER, 1ms);
|
||||
}
|
||||
|
||||
void UpdateAI(uint32 diff) override
|
||||
{
|
||||
_events.Update(diff);
|
||||
|
||||
if (me->HasUnitState(UNIT_STATE_CASTING))
|
||||
return;
|
||||
|
||||
while (uint32 eventId = _events.ExecuteEvent())
|
||||
{
|
||||
switch (eventId)
|
||||
{
|
||||
case EVENT_START_PATH_1:
|
||||
me->SetControlled(false, UNIT_STATE_ROOT);
|
||||
_currentPath = PATH_KRENNAN_TREE;
|
||||
me->GetMotionMaster()->MoveSmoothPath(pathSize1, greymanesHorsePath1, pathSize1);
|
||||
break;
|
||||
case EVENT_JUMP_TO_KRENNAN:
|
||||
me->GetMotionMaster()->MoveJump(GreymanesHorseJumpPos, 16.0f, 14.00968f);
|
||||
_events.ScheduleEvent(EVENT_ANNOUNCE_RESCUE, 2s);
|
||||
break;
|
||||
case EVENT_ANNOUNCE_RESCUE:
|
||||
me->SetControlled(true, UNIT_STATE_ROOT);
|
||||
if (Unit* passenger = me->GetVehicleKit()->GetPassenger(0))
|
||||
{
|
||||
Talk(SAY_ANNOUNCE_RESCUE, passenger);
|
||||
if (Creature* krennan = me->FindNearestCreature(NPC_TRAPPED_KRENNAN, 30.0f, true))
|
||||
krennan->AI()->Talk(SAY_TRAPPED, passenger);
|
||||
}
|
||||
break;
|
||||
case EVENT_START_PATH_2:
|
||||
me->SetControlled(true, UNIT_STATE_ROOT);
|
||||
_currentPath = PATH_KRENNAN_BACK;
|
||||
me->GetMotionMaster()->MoveSmoothPath(pathSize2, greymanesHorsePath2, pathSize2);
|
||||
break;
|
||||
case EVENT_DISMOUNT_PLAYER:
|
||||
if (Unit* passenger = me->GetVehicleKit()->GetPassenger(1))
|
||||
{
|
||||
if (Unit* player = me->GetVehicleKit()->GetPassenger(0))
|
||||
{
|
||||
if (Creature* krennan = passenger->ToCreature())
|
||||
{
|
||||
std::vector<Unit*> storedAttackers;
|
||||
|
||||
for (Unit* attacker : me->getAttackers())
|
||||
storedAttackers.push_back(attacker);
|
||||
|
||||
for (Unit* attacker : storedAttackers)
|
||||
{
|
||||
if (Creature* creature = attacker->ToCreature())
|
||||
if (creature->IsAIEnabled())
|
||||
creature->AI()->EnterEvadeMode();
|
||||
}
|
||||
|
||||
player->ExitVehicle();
|
||||
krennan->AddUnitState(UNIT_STATE_ROOT);
|
||||
krennan->ExitVehicle();
|
||||
krennan->AI()->Talk(SAY_RESCUED, me);
|
||||
krennan->DespawnOrUnsummon(7s);
|
||||
}
|
||||
}
|
||||
}
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
private:
|
||||
EventMap _events;
|
||||
uint32 _currentPath;
|
||||
};
|
||||
|
||||
/*######
|
||||
## Quest 14212 - Sacrifices
|
||||
######*/
|
||||
|
||||
enum CrowleysHorse
|
||||
{
|
||||
EVENT_JUMP_OVER_BARRICADES_1 = 1,
|
||||
EVENT_JUMP_OVER_BARRICADES_2,
|
||||
EVENT_MOVE_PATH_MAIN_1,
|
||||
EVENT_MOVE_PATH_MAIN_2,
|
||||
EVENT_MOVE_OFF_PATH = 6,
|
||||
|
||||
PATH_ID_CROWLEYS_HORSE_1 = 352310,
|
||||
PATH_ID_CROWLEYS_HORSE_2 = 352311,
|
||||
PATH_ID_CROWLEYS_HORSE_3 = 444280,
|
||||
|
||||
SPELL_THROW_TORCH = 67063,
|
||||
NPC_CROWLEYS_HORSE_2 = 44428,
|
||||
};
|
||||
|
||||
static Position const CrowleysHorseJumpPos = { -1714.762f, 1673.16f, 20.49182f };
|
||||
static Position const CrowleysHorseJumpPos2 = { -1566.71f, 1708.04f, 20.4849f };
|
||||
|
||||
static Position const CrowleysHorsePath1[] =
|
||||
{
|
||||
{ -1686.12f, 1655.64f, 20.6106f },
|
||||
{ -1668.16f, 1639.43f, 20.6106f },
|
||||
{ -1666.63f, 1628.86f, 20.5327f },
|
||||
{ -1670.76f, 1617.43f, 20.6106f },
|
||||
{ -1693.65f, 1592.64f, 20.6106f },
|
||||
{ -1708.02f, 1590.55f, 20.6104f },
|
||||
{ -1719.37f, 1595.65f, 20.6104f },
|
||||
{ -1720.57f, 1610.14f, 20.6074f },
|
||||
{ -1711.90f, 1629.70f, 20.6074f },
|
||||
{ -1698.15f, 1656.48f, 20.6106f },
|
||||
{ -1696.42f, 1682.01f, 20.5262f },
|
||||
{ -1684.68f, 1700.12f, 20.4648f },
|
||||
{ -1658.31f, 1710.31f, 20.5898f },
|
||||
{ -1625.48f, 1712.30f, 21.8051f },
|
||||
{ -1592.09f, 1710.71f, 20.5899f }
|
||||
};
|
||||
|
||||
|
||||
struct npc_crowleys_horse : public VehicleAI
|
||||
{
|
||||
npc_crowleys_horse(Creature* creature) : VehicleAI(creature), _currentPath(0) { }
|
||||
|
||||
void PassengerBoarded(Unit* passenger, int8 /*seatId*/, bool apply) override
|
||||
{
|
||||
if (apply && passenger->GetTypeId() == TYPEID_PLAYER && me->GetEntry() != NPC_CROWLEYS_HORSE_2)
|
||||
{
|
||||
me->SetControlled(true, UNIT_STATE_ROOT);
|
||||
_events.ScheduleEvent(EVENT_JUMP_OVER_BARRICADES_1, 2s);
|
||||
}
|
||||
else if (apply && passenger->GetTypeId() == TYPEID_PLAYER)
|
||||
{
|
||||
me->SetControlled(true, UNIT_STATE_ROOT);
|
||||
_events.ScheduleEvent(EVENT_MOVE_OFF_PATH, 2s);
|
||||
}
|
||||
}
|
||||
|
||||
void MovementInform(uint32 type, uint32 pointId) override
|
||||
{
|
||||
if (type == EFFECT_MOTION_TYPE && pointId == PATH_ID_CROWLEYS_HORSE_1 && _currentPath == PATH_ID_CROWLEYS_HORSE_1)
|
||||
_events.ScheduleEvent(EVENT_JUMP_OVER_BARRICADES_2, 1s);
|
||||
else if (type == WAYPOINT_MOTION_TYPE && pointId == 15 && _currentPath == PATH_ID_CROWLEYS_HORSE_2)
|
||||
_events.ScheduleEvent(EVENT_DISMOUNT_PLAYER, 1ms);
|
||||
else if (type == WAYPOINT_MOTION_TYPE && pointId == 16 && _currentPath == PATH_ID_CROWLEYS_HORSE_3)
|
||||
_events.ScheduleEvent(EVENT_DISMOUNT_PLAYER, 1ms);
|
||||
}
|
||||
|
||||
void UpdateAI(uint32 diff) override
|
||||
{
|
||||
_events.Update(diff);
|
||||
|
||||
while (uint32 eventId = _events.ExecuteEvent())
|
||||
{
|
||||
switch (eventId)
|
||||
{
|
||||
case EVENT_JUMP_OVER_BARRICADES_1:
|
||||
me->SetControlled(false, UNIT_STATE_ROOT);
|
||||
me->GetMotionMaster()->MoveJump(CrowleysHorseJumpPos, 16.0f, 18.56182f);
|
||||
_events.ScheduleEvent(EVENT_MOVE_PATH_MAIN_1, 2s);
|
||||
break;
|
||||
case EVENT_MOVE_PATH_MAIN_1:
|
||||
_currentPath = PATH_ID_CROWLEYS_HORSE_1;
|
||||
me->GetMotionMaster()->MoveSmoothPath(
|
||||
PATH_ID_CROWLEYS_HORSE_1,
|
||||
CrowleysHorsePath1,
|
||||
15);
|
||||
break;
|
||||
case EVENT_JUMP_OVER_BARRICADES_2:
|
||||
me->GetMotionMaster()->MoveJump(CrowleysHorseJumpPos2, 16.0f, 18.56182f);
|
||||
_events.ScheduleEvent(EVENT_MOVE_PATH_MAIN_2, 2s);
|
||||
break;
|
||||
case EVENT_MOVE_PATH_MAIN_2:
|
||||
_currentPath = PATH_ID_CROWLEYS_HORSE_2;
|
||||
me->GetMotionMaster()->MovePath(PATH_ID_CROWLEYS_HORSE_2, false);
|
||||
break;
|
||||
case EVENT_DISMOUNT_PLAYER:
|
||||
{
|
||||
std::set<Unit*> attackersCopy = me->getAttackers();
|
||||
for (Unit* attacker : attackersCopy)
|
||||
{
|
||||
if (Creature* creature = attacker->ToCreature())
|
||||
if (creature->IsAIEnabled())
|
||||
creature->AI()->EnterEvadeMode();
|
||||
}
|
||||
|
||||
me->RemoveAurasDueToSpell(VEHICLE_SPELL_RIDE_HARDCODED);
|
||||
me->DespawnOrUnsummon(5s);
|
||||
break;
|
||||
}
|
||||
case EVENT_MOVE_OFF_PATH:
|
||||
me->SetControlled(false, UNIT_STATE_ROOT);
|
||||
_currentPath = PATH_ID_CROWLEYS_HORSE_3;
|
||||
me->GetMotionMaster()->MovePath(PATH_ID_CROWLEYS_HORSE_3, false);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
private:
|
||||
EventMap _events;
|
||||
uint32 _currentPath;
|
||||
};
|
||||
|
||||
enum GileanCrow
|
||||
{
|
||||
SPELL_GILNEAN_CROW = 93275,
|
||||
EVENT_APPLY_HOVER_BYTES = 1,
|
||||
EVENT_FLY_AWAY_1 = 2,
|
||||
EVENT_FLY_AWAY_2 = 3,
|
||||
|
||||
POINT_NONE = 0,
|
||||
POINT_CROW_FLIGHT = 1
|
||||
};
|
||||
|
||||
struct npc_gilnean_crow : public PassiveAI
|
||||
{
|
||||
npc_gilnean_crow(Creature* creature) : PassiveAI(creature) { }
|
||||
|
||||
void SpellHit(WorldObject* /*caster*/, SpellInfo const* spell) override
|
||||
{
|
||||
if (spell->Id == SPELL_GILNEAN_CROW)
|
||||
_events.ScheduleEvent(EVENT_APPLY_HOVER_BYTES, 500ms);
|
||||
}
|
||||
|
||||
void MovementInform(uint32 type, uint32 pointId) override
|
||||
{
|
||||
if (type == POINT_MOTION_TYPE && pointId == POINT_CROW_FLIGHT)
|
||||
{
|
||||
Position pos = me->GetRandomNearPosition(8.0f);
|
||||
pos.m_positionZ = me->GetPositionZ() + 10.0f;
|
||||
me->GetMotionMaster()->MovePoint(POINT_NONE, pos);
|
||||
me->DespawnOrUnsummon(14s);
|
||||
}
|
||||
}
|
||||
|
||||
void UpdateAI(uint32 diff) override
|
||||
{
|
||||
_events.Update(diff);
|
||||
|
||||
while (uint32 eventId = _events.ExecuteEvent())
|
||||
{
|
||||
switch (eventId)
|
||||
{
|
||||
case EVENT_APPLY_HOVER_BYTES:
|
||||
me->SetStandState(UNIT_STAND_STATE_SIT);
|
||||
me->SetAnimTier(AnimTier::Submerged);
|
||||
_events.ScheduleEvent(EVENT_FLY_AWAY_1, 1s);
|
||||
break;
|
||||
case EVENT_FLY_AWAY_1:
|
||||
{
|
||||
Position pos = me->GetRandomNearPosition(7.0f);
|
||||
pos.m_positionZ = me->GetPositionZ() + 8.0f;
|
||||
me->GetMotionMaster()->MovePoint(POINT_CROW_FLIGHT, pos);
|
||||
break;
|
||||
}
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
private:
|
||||
EventMap _events;
|
||||
};
|
||||
}
|
||||
|
||||
void AddSC_gilneas_chapter_1()
|
||||
{
|
||||
using namespace Gilneas::Chapter1;
|
||||
RegisterCreatureAI(npc_frightened_citizen);
|
||||
RegisterCreatureAI(npc_worgen_runt);
|
||||
RegisterCreatureAI(npc_josiah_avery);
|
||||
RegisterCreatureAI(npc_greymanes_horse);
|
||||
RegisterCreatureAI(npc_crowleys_horse);
|
||||
RegisterCreatureAI(npc_gilnean_crow);
|
||||
}
|
||||
+774
@@ -0,0 +1,774 @@
|
||||
/*
|
||||
* This file is part of the TrinityCore 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 "gilneas.h"
|
||||
#include "ScriptMgr.h"
|
||||
#include "CombatAI.h"
|
||||
#include "MotionMaster.h"
|
||||
#include "ObjectAccessor.h"
|
||||
#include "Player.h"
|
||||
#include "PassiveAI.h"
|
||||
#include "PhasingHandler.h"
|
||||
#include "ScriptedCreature.h"
|
||||
#include "SpellInfo.h"
|
||||
#include "SpellScript.h"
|
||||
#include "TemporarySummon.h"
|
||||
#include "Vehicle.h"
|
||||
|
||||
namespace Gilneas::Chapter1
|
||||
{
|
||||
|
||||
/*######
|
||||
## Quest 14098 - Evacuate the Merchant Square
|
||||
######*/
|
||||
|
||||
enum FrightenedCitizen
|
||||
{
|
||||
SAY_FRIGHTENED_CITIZEN_RESCUE = 0,
|
||||
|
||||
NPC_EVACUATION_STALKER_FIRST = 35830,
|
||||
NPC_EVACUATION_STALKER_NEAR = 35010,
|
||||
NPC_EVACUATION_STALKER_FAR = 35011,
|
||||
NPC_RAMPAGING_WORGEN = 35660,
|
||||
|
||||
CREDIT_35830 = 35830,
|
||||
|
||||
POINT_STALKER_FIRST = 1,
|
||||
POINT_STALKER_NEAR = 2,
|
||||
POINT_STALKER_FAR = 3,
|
||||
|
||||
EVENT_TALK_FRIGHTENED = 1,
|
||||
EVENT_MOVE_TO_NEAR_STALKER = 2,
|
||||
EVENT_MOVE_TO_FAR_STALKER = 3,
|
||||
EVENT_DESPAWN = 4
|
||||
};
|
||||
|
||||
struct npc_frightened_citizen : public PassiveAI
|
||||
{
|
||||
npc_frightened_citizen(Creature* creature) : PassiveAI(creature) { }
|
||||
|
||||
void IsSummonedBy(Unit* /*summoner*/) override
|
||||
{
|
||||
if (Creature* stalkerNear = me->FindNearestCreature(NPC_EVACUATION_STALKER_FIRST, 20.0f))
|
||||
me->GetMotionMaster()->MovePoint(POINT_STALKER_FIRST, stalkerNear->GetPosition(), true);
|
||||
}
|
||||
|
||||
void MovementInform(uint32 type, uint32 id) override
|
||||
{
|
||||
if (type != POINT_MOTION_TYPE)
|
||||
return;
|
||||
|
||||
switch (id)
|
||||
{
|
||||
case POINT_STALKER_FIRST:
|
||||
_events.ScheduleEvent(EVENT_TALK_FRIGHTENED, 1s);
|
||||
break;
|
||||
case POINT_STALKER_NEAR:
|
||||
_events.ScheduleEvent(EVENT_MOVE_TO_FAR_STALKER, 1ms);
|
||||
break;
|
||||
case POINT_STALKER_FAR:
|
||||
_events.ScheduleEvent(EVENT_DESPAWN, 1ms);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void UpdateAI(uint32 diff) override
|
||||
{
|
||||
_events.Update(diff);
|
||||
|
||||
while (uint32 eventId = _events.ExecuteEvent())
|
||||
{
|
||||
switch (eventId)
|
||||
{
|
||||
case EVENT_TALK_FRIGHTENED:
|
||||
if (Unit* summoner = me->ToTempSummon()->GetSummoner())
|
||||
{
|
||||
if (Player* player = summoner->ToPlayer())
|
||||
{
|
||||
player->KilledMonsterCredit(CREDIT_35830);
|
||||
Talk(SAY_FRIGHTENED_CITIZEN_RESCUE, summoner);
|
||||
}
|
||||
}
|
||||
_events.ScheduleEvent(EVENT_MOVE_TO_NEAR_STALKER, 2s);
|
||||
break;
|
||||
case EVENT_MOVE_TO_NEAR_STALKER:
|
||||
if (Creature* stalker = me->FindNearestCreature(NPC_EVACUATION_STALKER_FAR, 50.0f))
|
||||
me->GetMotionMaster()->MovePoint(POINT_STALKER_FAR, stalker->GetPosition(), true);
|
||||
else if (Creature* stalker = me->FindNearestCreature(NPC_EVACUATION_STALKER_NEAR, 100.0f))
|
||||
me->GetMotionMaster()->MovePoint(POINT_STALKER_NEAR, stalker->GetPosition(), true);
|
||||
break;
|
||||
case EVENT_MOVE_TO_FAR_STALKER:
|
||||
if (Creature* stalker = me->FindNearestCreature(NPC_EVACUATION_STALKER_FAR, 500.0f))
|
||||
me->GetMotionMaster()->MovePoint(POINT_STALKER_FAR, stalker->GetPosition(), true);
|
||||
break;
|
||||
case EVENT_DESPAWN:
|
||||
me->DespawnOrUnsummon();
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
private:
|
||||
EventMap _events;
|
||||
};
|
||||
|
||||
/*######
|
||||
## Quest 14154 - By the Skin of his Teeth
|
||||
######*/
|
||||
|
||||
enum WorgenRunt
|
||||
{
|
||||
EVENT_JUMP_TO_PRISON = 1,
|
||||
EVENT_AGGRO_PLAYER = 2,
|
||||
EVENT_FORCE_DESPAWN = 3,
|
||||
|
||||
PHASE_ROOF = 0,
|
||||
PHASE_COMBAT = 1,
|
||||
|
||||
WORGEN_ID_ROOF_1 = 0,
|
||||
WORGEN_ID_ROOF_2 = 1,
|
||||
WORGEN_ID_ROOF_3 = 2,
|
||||
WORGEN_ID_ROOF_4 = 3,
|
||||
WORGEN_ID_ROOF_5 = 4,
|
||||
WORGEN_ID_ROOF_6 = 5,
|
||||
WORGEN_ID_ROOF_7 = 6,
|
||||
|
||||
WORGEN_ID_CATHEDRAL_1 = 7,
|
||||
WORGEN_ID_CATHEDRAL_2 = 8,
|
||||
WORGEN_ID_CATHEDRAL_3 = 9,
|
||||
WORGEN_ID_CATHEDRAL_4 = 10,
|
||||
WORGEN_ID_CATHEDRAL_5 = 11,
|
||||
WORGEN_ID_CATHEDRAL_6 = 12,
|
||||
WORGEN_ID_CATHEDRAL_7 = 13,
|
||||
WORGEN_ID_CATHEDRAL_8 = 14,
|
||||
WORGEN_ID_CATHEDRAL_9 = 15,
|
||||
|
||||
NPC_WORGEN_RUNT_SPELL = 35188,
|
||||
};
|
||||
|
||||
static Position const RuntSpellSummonJumpPos = { -1671.915f, 1446.734f, 52.28712f };
|
||||
|
||||
struct npc_worgen_runt : public ScriptedAI
|
||||
{
|
||||
npc_worgen_runt(Creature* creature) : ScriptedAI(creature), _worgenID(0), _wayPointCounter(0), _jumped(false) { }
|
||||
|
||||
void JustEngagedWith(Unit* /*who*/) override
|
||||
{
|
||||
_events.SetPhase(PHASE_COMBAT);
|
||||
}
|
||||
|
||||
void IsSummonedBy(Unit* summoner) override
|
||||
{
|
||||
me->setActive(true); // we are in a phased and cut off map so we're fine to use that here
|
||||
_events.SetPhase(PHASE_ROOF);
|
||||
_events.ScheduleEvent(EVENT_FORCE_DESPAWN, 70s, 0, PHASE_ROOF);
|
||||
_playerGuid = summoner->GetGUID();
|
||||
if (me->GetEntry() == NPC_WORGEN_RUNT_SPELL)
|
||||
me->GetMotionMaster()->MoveJump(RuntSpellSummonJumpPos, 16.0f, 4.371286f);
|
||||
}
|
||||
|
||||
void DoAction(int32 action) override
|
||||
{
|
||||
_worgenID = action;
|
||||
switch (_worgenID) // ugly as fuck but needed for individual controls
|
||||
{
|
||||
// House roof cases
|
||||
case WORGEN_ID_ROOF_1:
|
||||
_wayPointCounter = runtHousePathSize1;
|
||||
me->GetMotionMaster()->MoveSmoothPath(runtHousePathSize1, worgenRuntHousePath1, runtHousePathSize1);
|
||||
break;
|
||||
case WORGEN_ID_ROOF_2:
|
||||
_wayPointCounter = runtHousePathSize2;
|
||||
me->GetMotionMaster()->MoveSmoothPath(runtHousePathSize2, worgenRuntHousePath2, runtHousePathSize2);
|
||||
break;
|
||||
case WORGEN_ID_ROOF_3:
|
||||
_wayPointCounter = runtHousePathSize3;
|
||||
me->GetMotionMaster()->MoveSmoothPath(runtHousePathSize3, worgenRuntHousePath3, runtHousePathSize3);
|
||||
break;
|
||||
case WORGEN_ID_ROOF_4:
|
||||
_wayPointCounter = runtHousePathSize4;
|
||||
me->GetMotionMaster()->MoveSmoothPath(runtHousePathSize4, worgenRuntHousePath4, runtHousePathSize4);
|
||||
break;
|
||||
case WORGEN_ID_ROOF_5:
|
||||
_wayPointCounter = runtHousePathSize5;
|
||||
me->GetMotionMaster()->MoveSmoothPath(runtHousePathSize5, worgenRuntHousePath5, runtHousePathSize5);
|
||||
break;
|
||||
case WORGEN_ID_ROOF_6:
|
||||
_wayPointCounter = runtHousePathSize6;
|
||||
me->GetMotionMaster()->MoveSmoothPath(runtHousePathSize6, worgenRuntHousePath6, runtHousePathSize6);
|
||||
break;
|
||||
case WORGEN_ID_ROOF_7:
|
||||
_wayPointCounter = runtHousePathSize7;
|
||||
me->GetMotionMaster()->MoveSmoothPath(runtHousePathSize7, worgenRuntHousePath7, runtHousePathSize7);
|
||||
break;
|
||||
// Cathedral cases
|
||||
case WORGEN_ID_CATHEDRAL_1:
|
||||
_wayPointCounter = runtCathedralPathSize1;
|
||||
me->GetMotionMaster()->MoveSmoothPath(runtCathedralPathSize1, worgenRuntCathedralPath1, runtCathedralPathSize1);
|
||||
break;
|
||||
case WORGEN_ID_CATHEDRAL_2:
|
||||
_wayPointCounter = runtCathedralPathSize2;
|
||||
me->GetMotionMaster()->MoveSmoothPath(runtCathedralPathSize2, worgenRuntCathedralPath1, runtCathedralPathSize2);
|
||||
break;
|
||||
case WORGEN_ID_CATHEDRAL_3:
|
||||
_wayPointCounter = runtCathedralPathSize3;
|
||||
me->GetMotionMaster()->MoveSmoothPath(runtCathedralPathSize3, worgenRuntCathedralPath1, runtCathedralPathSize3);
|
||||
break;
|
||||
case WORGEN_ID_CATHEDRAL_4:
|
||||
_wayPointCounter = runtCathedralPathSize4;
|
||||
me->GetMotionMaster()->MoveSmoothPath(runtCathedralPathSize4, worgenRuntCathedralPath1, runtCathedralPathSize4);
|
||||
break;
|
||||
case WORGEN_ID_CATHEDRAL_5:
|
||||
_wayPointCounter = runtCathedralPathSize5;
|
||||
me->GetMotionMaster()->MoveSmoothPath(runtCathedralPathSize5, worgenRuntCathedralPath1, runtCathedralPathSize5);
|
||||
break;
|
||||
case WORGEN_ID_CATHEDRAL_6:
|
||||
_wayPointCounter = runtCathedralPathSize6;
|
||||
me->GetMotionMaster()->MoveSmoothPath(runtCathedralPathSize6, worgenRuntCathedralPath1, runtCathedralPathSize6);
|
||||
break;
|
||||
case WORGEN_ID_CATHEDRAL_7:
|
||||
_wayPointCounter = runtCathedralPathSize7;
|
||||
me->GetMotionMaster()->MoveSmoothPath(runtCathedralPathSize7, worgenRuntCathedralPath1, runtCathedralPathSize7);
|
||||
break;
|
||||
case WORGEN_ID_CATHEDRAL_8:
|
||||
_wayPointCounter = runtCathedralPathSize8;
|
||||
me->GetMotionMaster()->MoveSmoothPath(runtCathedralPathSize8, worgenRuntCathedralPath1, runtCathedralPathSize8);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void JustDied(Unit* /*killer*/) override
|
||||
{
|
||||
me->DespawnOrUnsummon(5s);
|
||||
}
|
||||
|
||||
void MovementInform(uint32 type, uint32 pointId) override
|
||||
{
|
||||
if (type == EFFECT_MOTION_TYPE && pointId == _wayPointCounter && !_jumped)
|
||||
{
|
||||
_jumped = true;
|
||||
_events.ScheduleEvent(EVENT_JUMP_TO_PRISON, 1ms);
|
||||
}
|
||||
}
|
||||
|
||||
void UpdateAI(uint32 diff) override
|
||||
{
|
||||
if (!UpdateVictim() && !_events.IsInPhase(PHASE_ROOF))
|
||||
return;
|
||||
|
||||
_events.Update(diff);
|
||||
|
||||
while (uint32 eventId = _events.ExecuteEvent())
|
||||
{
|
||||
switch (eventId)
|
||||
{
|
||||
case EVENT_FORCE_DESPAWN:
|
||||
me->DespawnOrUnsummon();
|
||||
break;
|
||||
case EVENT_JUMP_TO_PRISON:
|
||||
me->GetMotionMaster()->MoveJump(worgenRuntJumpPos[_worgenID], 16.0f, _worgenID < WORGEN_ID_CATHEDRAL_1 ? 19.2911f : frand(3.945607f, 4.852813f));
|
||||
me->SetHomePosition(worgenRuntJumpPos[_worgenID]);
|
||||
_events.ScheduleEvent(EVENT_AGGRO_PLAYER, 2s);
|
||||
break;
|
||||
case EVENT_AGGRO_PLAYER:
|
||||
{
|
||||
Creature* crowley = me->FindNearestCreature(35077, 100.0f, true);
|
||||
Creature* tobias = me->FindNearestCreature(35124, 100.0f, true);
|
||||
|
||||
if (crowley && tobias)
|
||||
{
|
||||
if (me->GetDistance(crowley) <= me->GetDistance(tobias))
|
||||
AttackStart(crowley);
|
||||
else
|
||||
AttackStart(tobias);
|
||||
}
|
||||
else if (crowley)
|
||||
AttackStart(crowley);
|
||||
else if (tobias)
|
||||
AttackStart(tobias);
|
||||
|
||||
break;
|
||||
}
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
DoMeleeAttackIfReady();
|
||||
}
|
||||
private:
|
||||
uint32 _worgenID;
|
||||
uint32 _wayPointCounter;
|
||||
bool _jumped;
|
||||
ObjectGuid _playerGuid;
|
||||
EventMap _events;
|
||||
};
|
||||
|
||||
/*######
|
||||
## Quest 14159 - The Rebel Lord's Arsenal
|
||||
######*/
|
||||
|
||||
enum TheRebelLordsArsena
|
||||
{
|
||||
PHASE_ID_SUMMON = 170,
|
||||
PHASE_ID_WOUND = 171,
|
||||
NPC_LORNA_CROWLEY = 35378,
|
||||
NPC_GENERIC_TRIGGER_LAB = 35374,
|
||||
|
||||
SPELL_SHOOT_INSTAKILL = 67593,
|
||||
SPELL_COSMETIC_ATTACK = 42880,
|
||||
SPELL_PULL_TO = 67357,
|
||||
|
||||
EVENT_COSMETIC_ATTACK = 1,
|
||||
EVENT_JUMP_TO_PLAYER,
|
||||
EVENT_SHOOT_JOSIAH
|
||||
};
|
||||
|
||||
static Position const JosiahJumpPos = { -1796.63f, 1427.73f, 12.4624f };
|
||||
|
||||
struct npc_josiah_avery : public PassiveAI
|
||||
{
|
||||
npc_josiah_avery(Creature* creature) : PassiveAI(creature) { }
|
||||
|
||||
void IsSummonedBy(Unit* summoner) override
|
||||
{
|
||||
PhasingHandler::AddPhase(me, PHASE_ID_SUMMON, true);
|
||||
PhasingHandler::AddPhase(me, PHASE_ID_WOUND, true);
|
||||
_playerGuid = summoner->GetGUID();
|
||||
_events.ScheduleEvent(EVENT_COSMETIC_ATTACK, 500ms);
|
||||
}
|
||||
|
||||
void JustDied(Unit* /*killer*/) override
|
||||
{
|
||||
me->DespawnOrUnsummon(5s);
|
||||
}
|
||||
|
||||
void UpdateAI(uint32 diff) override
|
||||
{
|
||||
_events.Update(diff);
|
||||
|
||||
while (uint32 eventId = _events.ExecuteEvent())
|
||||
{
|
||||
switch (eventId)
|
||||
{
|
||||
case EVENT_COSMETIC_ATTACK:
|
||||
if (Player* player = ObjectAccessor::GetPlayer(*me, _playerGuid))
|
||||
{
|
||||
DoCast(player, SPELL_COSMETIC_ATTACK);
|
||||
if (Creature* lorna = me->FindNearestCreature(NPC_LORNA_CROWLEY, 30.0f, true))
|
||||
if (Creature* labTrigger = lorna->FindNearestCreature(NPC_GENERIC_TRIGGER_LAB, 5.0f, true))
|
||||
labTrigger->CastSpell(player, SPELL_PULL_TO);
|
||||
|
||||
_events.ScheduleEvent(EVENT_JUMP_TO_PLAYER, 1s);
|
||||
}
|
||||
break;
|
||||
case EVENT_JUMP_TO_PLAYER:
|
||||
me->GetMotionMaster()->MoveJump(JosiahJumpPos, 10.0f, 14.18636f);
|
||||
_events.ScheduleEvent(EVENT_SHOOT_JOSIAH, 500ms);
|
||||
break;
|
||||
case EVENT_SHOOT_JOSIAH:
|
||||
if (Creature* lorna = me->FindNearestCreature(NPC_LORNA_CROWLEY, 30.0f, true))
|
||||
lorna->CastSpell(me, SPELL_SHOOT_INSTAKILL, true);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
private:
|
||||
ObjectGuid _playerGuid;
|
||||
EventMap _events;
|
||||
};
|
||||
|
||||
/*######
|
||||
## Quest 14293 - Save Krennan Aranas
|
||||
######*/
|
||||
|
||||
static Position const GreymanesHorseJumpPos = { -1676.16f, 1346.19f, 15.1349f };
|
||||
|
||||
enum GreymanesHorse
|
||||
{
|
||||
SAY_ANNOUNCE_RESCUE = 0,
|
||||
SAY_RESCUED = 0,
|
||||
SAY_TRAPPED = 0,
|
||||
|
||||
PATH_KRENNAN_TREE = 0,
|
||||
PATH_KRENNAN_BACK = 1,
|
||||
|
||||
EVENT_START_PATH_1 = 1,
|
||||
EVENT_START_PATH_2,
|
||||
EVENT_JUMP_TO_KRENNAN,
|
||||
EVENT_ANNOUNCE_RESCUE,
|
||||
EVENT_DISMOUNT_PLAYER,
|
||||
|
||||
NPC_RESCUED_KRENNAN = 35907,
|
||||
NPC_TRAPPED_KRENNAN = 35753,
|
||||
};
|
||||
|
||||
struct npc_greymanes_horse : public VehicleAI
|
||||
{
|
||||
npc_greymanes_horse(Creature* creature) : VehicleAI(creature), _currentPath(PATH_KRENNAN_TREE) { }
|
||||
|
||||
void DamageTaken(Unit* /*attacker*/, uint32& damage) override
|
||||
{
|
||||
if (me->GetHealth() <= 1)
|
||||
damage = 0;
|
||||
else if (damage >= me->GetHealth())
|
||||
damage = me->GetHealth() - 1;
|
||||
}
|
||||
|
||||
void PassengerBoarded(Unit* passenger, int8 /*seatId*/, bool apply) override
|
||||
{
|
||||
if (apply && passenger->GetTypeId() == TYPEID_PLAYER)
|
||||
{
|
||||
me->SetControlled(true, UNIT_STATE_ROOT);
|
||||
_events.ScheduleEvent(EVENT_START_PATH_1, 1s);
|
||||
}
|
||||
else if (apply && passenger->GetEntry() == NPC_RESCUED_KRENNAN)
|
||||
_events.ScheduleEvent(EVENT_START_PATH_2, 1s);
|
||||
}
|
||||
|
||||
void MovementInform(uint32 type, uint32 pointId) override
|
||||
{
|
||||
if (type == EFFECT_MOTION_TYPE && pointId == pathSize1 && _currentPath == PATH_KRENNAN_TREE)
|
||||
_events.ScheduleEvent(EVENT_JUMP_TO_KRENNAN, 1ms);
|
||||
else if (type == EFFECT_MOTION_TYPE && pointId == pathSize2 && _currentPath == PATH_KRENNAN_BACK)
|
||||
_events.ScheduleEvent(EVENT_DISMOUNT_PLAYER, 1ms);
|
||||
}
|
||||
|
||||
void UpdateAI(uint32 diff) override
|
||||
{
|
||||
_events.Update(diff);
|
||||
|
||||
if (me->HasUnitState(UNIT_STATE_CASTING))
|
||||
return;
|
||||
|
||||
while (uint32 eventId = _events.ExecuteEvent())
|
||||
{
|
||||
switch (eventId)
|
||||
{
|
||||
case EVENT_START_PATH_1:
|
||||
me->SetControlled(false, UNIT_STATE_ROOT);
|
||||
_currentPath = PATH_KRENNAN_TREE;
|
||||
me->GetMotionMaster()->MoveSmoothPath(pathSize1, greymanesHorsePath1, pathSize1);
|
||||
break;
|
||||
case EVENT_JUMP_TO_KRENNAN:
|
||||
me->GetMotionMaster()->MoveJump(GreymanesHorseJumpPos, 16.0f, 14.00968f);
|
||||
_events.ScheduleEvent(EVENT_ANNOUNCE_RESCUE, 2s);
|
||||
break;
|
||||
case EVENT_ANNOUNCE_RESCUE:
|
||||
me->SetControlled(true, UNIT_STATE_ROOT);
|
||||
if (Unit* passenger = me->GetVehicleKit()->GetPassenger(0))
|
||||
{
|
||||
Talk(SAY_ANNOUNCE_RESCUE, passenger);
|
||||
if (Creature* krennan = me->FindNearestCreature(NPC_TRAPPED_KRENNAN, 30.0f, true))
|
||||
krennan->AI()->Talk(SAY_TRAPPED, passenger);
|
||||
}
|
||||
break;
|
||||
case EVENT_START_PATH_2:
|
||||
me->SetControlled(true, UNIT_STATE_ROOT);
|
||||
_currentPath = PATH_KRENNAN_BACK;
|
||||
me->GetMotionMaster()->MoveSmoothPath(pathSize2, greymanesHorsePath2, pathSize2);
|
||||
break;
|
||||
case EVENT_DISMOUNT_PLAYER:
|
||||
if (Unit* passenger = me->GetVehicleKit()->GetPassenger(1))
|
||||
{
|
||||
if (Unit* player = me->GetVehicleKit()->GetPassenger(0))
|
||||
{
|
||||
if (Creature* krennan = passenger->ToCreature())
|
||||
{
|
||||
std::vector<Unit*> storedAttackers;
|
||||
|
||||
for (Unit* attacker : me->getAttackers())
|
||||
storedAttackers.push_back(attacker);
|
||||
|
||||
for (Unit* attacker : storedAttackers)
|
||||
{
|
||||
if (Creature* creature = attacker->ToCreature())
|
||||
if (creature->IsAIEnabled())
|
||||
creature->AI()->EnterEvadeMode();
|
||||
}
|
||||
|
||||
player->ExitVehicle();
|
||||
krennan->AddUnitState(UNIT_STATE_ROOT);
|
||||
krennan->ExitVehicle();
|
||||
krennan->AI()->Talk(SAY_RESCUED, me);
|
||||
krennan->DespawnOrUnsummon(7s);
|
||||
}
|
||||
}
|
||||
}
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
private:
|
||||
EventMap _events;
|
||||
uint32 _currentPath;
|
||||
};
|
||||
|
||||
/*######
|
||||
## Quest 14212 - Sacrifices
|
||||
######*/
|
||||
|
||||
enum CrowleysHorse
|
||||
{
|
||||
EVENT_JUMP_OVER_BARRICADES_1 = 1,
|
||||
EVENT_JUMP_OVER_BARRICADES_2,
|
||||
EVENT_MOVE_PATH_MAIN_1,
|
||||
EVENT_MOVE_PATH_MAIN_2,
|
||||
EVENT_MOVE_OFF_PATH = 6,
|
||||
|
||||
PATH_ID_CROWLEYS_HORSE_1 = 352310,
|
||||
PATH_ID_CROWLEYS_HORSE_2 = 352311,
|
||||
PATH_ID_CROWLEYS_HORSE_3 = 444280,
|
||||
|
||||
SPELL_THROW_TORCH = 67063,
|
||||
NPC_CROWLEYS_HORSE_2 = 44428,
|
||||
};
|
||||
|
||||
static Position const CrowleysHorseJumpPos = { -1714.762f, 1673.16f, 20.49182f };
|
||||
static Position const CrowleysHorseJumpPos2 = { -1566.71f, 1708.04f, 20.4849f };
|
||||
|
||||
static Position const CrowleysHorsePath1[] =
|
||||
{
|
||||
{ -1686.12f, 1655.64f, 20.6106f },
|
||||
{ -1668.16f, 1639.43f, 20.6106f },
|
||||
{ -1666.63f, 1628.86f, 20.5327f },
|
||||
{ -1670.76f, 1617.43f, 20.6106f },
|
||||
{ -1693.65f, 1592.64f, 20.6106f },
|
||||
{ -1708.02f, 1590.55f, 20.6104f },
|
||||
{ -1719.37f, 1595.65f, 20.6104f },
|
||||
{ -1720.57f, 1610.14f, 20.6074f },
|
||||
{ -1711.90f, 1629.70f, 20.6074f },
|
||||
{ -1698.15f, 1656.48f, 20.6106f },
|
||||
{ -1696.42f, 1682.01f, 20.5262f },
|
||||
{ -1684.68f, 1700.12f, 20.4648f },
|
||||
{ -1658.31f, 1710.31f, 20.5898f },
|
||||
{ -1625.48f, 1712.30f, 21.8051f },
|
||||
{ -1592.09f, 1710.71f, 20.5899f }
|
||||
};
|
||||
|
||||
|
||||
static Position const CrowleysHorsePath2[] =
|
||||
{
|
||||
{ -1545.08f, 1684.23f, 20.6099f },
|
||||
{ -1542.79f, 1656.08f, 20.6099f },
|
||||
{ -1543.0f, 1640.54f, 20.6099f },
|
||||
{ -1520.73f, 1618.1f, 20.6105f },
|
||||
{ -1498.79f, 1621.74f, 20.6105f },
|
||||
{ -1468.36f, 1630.78f, 20.6105f },
|
||||
{ -1441.65f, 1627.67f, 20.6105f },
|
||||
{ -1429.84f, 1616.63f, 20.5898f },
|
||||
{ -1417.99f, 1586.55f, 20.5898f },
|
||||
{ -1429.18f, 1548.78f, 20.8485f },
|
||||
{ -1447.42f, 1532.79f, 20.601f },
|
||||
{ -1463.1f, 1535.03f, 20.6105f },
|
||||
{ -1483.04f, 1544.15f, 20.6105f },
|
||||
{ -1505.16f, 1568.61f, 20.6105f },
|
||||
{ -1531.6f, 1581.87f, 26.5387f },
|
||||
{ -1540.59f, 1574.43f, 29.2067f },
|
||||
};
|
||||
|
||||
static Position const CrowleysHorsePath3[] =
|
||||
{
|
||||
{ -1512.36f, 1570.78f, 24.0671f },
|
||||
{ -1498.71f, 1565.43f, 20.8781f },
|
||||
{ -1487.86f, 1557.16f, 20.85f },
|
||||
{ -1474.18f, 1542.68f, 20.6105f },
|
||||
{ -1457.6f, 1532.64f, 20.601f },
|
||||
{ -1438.01f, 1525.31f, 20.8762f },
|
||||
{ -1424.2f, 1545.7f, 20.4657f },
|
||||
{ -1411.29f, 1556.16f, 20.557f },
|
||||
{ -1414.19f, 1577.65f, 20.5898f },
|
||||
{ -1427.47f, 1617.3f, 20.5898f },
|
||||
{ -1445.54f, 1633.9f, 20.6105f },
|
||||
{ -1470.71f, 1658.21f, 20.6105f },
|
||||
{ -1491.05f, 1658.74f, 20.6105f },
|
||||
{ -1515.33f, 1641.18f, 20.6099f },
|
||||
{ -1526.75f, 1630.37f, 20.6105f },
|
||||
{ -1528.42f, 1610.71f, 20.6105f },
|
||||
{ -1532.39f, 1585.36f, 26.538f },
|
||||
};
|
||||
|
||||
struct npc_crowleys_horse : public VehicleAI
|
||||
{
|
||||
npc_crowleys_horse(Creature* creature) : VehicleAI(creature), _currentPath(0) { }
|
||||
|
||||
void PassengerBoarded(Unit* passenger, int8 /*seatId*/, bool apply) override
|
||||
{
|
||||
if (apply && passenger->GetTypeId() == TYPEID_PLAYER && me->GetEntry() != NPC_CROWLEYS_HORSE_2)
|
||||
{
|
||||
me->SetControlled(true, UNIT_STATE_ROOT);
|
||||
_events.ScheduleEvent(EVENT_JUMP_OVER_BARRICADES_1, 2s);
|
||||
}
|
||||
else if (apply && passenger->GetTypeId() == TYPEID_PLAYER)
|
||||
{
|
||||
me->SetControlled(true, UNIT_STATE_ROOT);
|
||||
_events.ScheduleEvent(EVENT_MOVE_OFF_PATH, 2s);
|
||||
}
|
||||
}
|
||||
|
||||
void MovementInform(uint32 type, uint32 pointId) override
|
||||
{
|
||||
if (type == EFFECT_MOTION_TYPE && pointId == PATH_ID_CROWLEYS_HORSE_1 && _currentPath == PATH_ID_CROWLEYS_HORSE_1)
|
||||
_events.ScheduleEvent(EVENT_JUMP_OVER_BARRICADES_2, 1s);
|
||||
else if (type == EFFECT_MOTION_TYPE && pointId == PATH_ID_CROWLEYS_HORSE_2 && _currentPath == PATH_ID_CROWLEYS_HORSE_2)
|
||||
_events.ScheduleEvent(EVENT_DISMOUNT_PLAYER, 1ms);
|
||||
else if (type == EFFECT_MOTION_TYPE && pointId == PATH_ID_CROWLEYS_HORSE_3 && _currentPath == PATH_ID_CROWLEYS_HORSE_3)
|
||||
_events.ScheduleEvent(EVENT_DISMOUNT_PLAYER, 1ms);
|
||||
}
|
||||
|
||||
void UpdateAI(uint32 diff) override
|
||||
{
|
||||
_events.Update(diff);
|
||||
|
||||
while (uint32 eventId = _events.ExecuteEvent())
|
||||
{
|
||||
switch (eventId)
|
||||
{
|
||||
case EVENT_JUMP_OVER_BARRICADES_1:
|
||||
me->SetControlled(false, UNIT_STATE_ROOT);
|
||||
me->GetMotionMaster()->MoveJump(CrowleysHorseJumpPos, 16.0f, 18.56182f);
|
||||
_events.ScheduleEvent(EVENT_MOVE_PATH_MAIN_1, 2s);
|
||||
break;
|
||||
case EVENT_MOVE_PATH_MAIN_1:
|
||||
_currentPath = PATH_ID_CROWLEYS_HORSE_1;
|
||||
me->GetMotionMaster()->MoveSmoothPath(
|
||||
PATH_ID_CROWLEYS_HORSE_1,
|
||||
CrowleysHorsePath1,
|
||||
15);
|
||||
break;
|
||||
case EVENT_JUMP_OVER_BARRICADES_2:
|
||||
me->GetMotionMaster()->MoveJump(CrowleysHorseJumpPos2, 16.0f, 18.56182f);
|
||||
_events.ScheduleEvent(EVENT_MOVE_PATH_MAIN_2, 2s);
|
||||
break;
|
||||
case EVENT_MOVE_PATH_MAIN_2:
|
||||
_currentPath = PATH_ID_CROWLEYS_HORSE_2;
|
||||
me->GetMotionMaster()->MoveSmoothPath(
|
||||
PATH_ID_CROWLEYS_HORSE_2,
|
||||
CrowleysHorsePath2,
|
||||
16);
|
||||
break;
|
||||
case EVENT_DISMOUNT_PLAYER:
|
||||
{
|
||||
std::set<Unit*> attackersCopy = me->getAttackers();
|
||||
for (Unit* attacker : attackersCopy)
|
||||
{
|
||||
if (Creature* creature = attacker->ToCreature())
|
||||
if (creature->IsAIEnabled())
|
||||
creature->AI()->EnterEvadeMode();
|
||||
}
|
||||
|
||||
me->RemoveAurasDueToSpell(VEHICLE_SPELL_RIDE_HARDCODED);
|
||||
me->DespawnOrUnsummon(5s);
|
||||
break;
|
||||
}
|
||||
case EVENT_MOVE_OFF_PATH:
|
||||
me->SetControlled(false, UNIT_STATE_ROOT);
|
||||
_currentPath = PATH_ID_CROWLEYS_HORSE_3;
|
||||
me->GetMotionMaster()->MoveSmoothPath(
|
||||
PATH_ID_CROWLEYS_HORSE_3,
|
||||
CrowleysHorsePath3,
|
||||
17);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
private:
|
||||
EventMap _events;
|
||||
uint32 _currentPath;
|
||||
};
|
||||
|
||||
enum GileanCrow
|
||||
{
|
||||
SPELL_GILNEAN_CROW = 93275,
|
||||
EVENT_APPLY_HOVER_BYTES = 1,
|
||||
EVENT_FLY_AWAY_1 = 2,
|
||||
EVENT_FLY_AWAY_2 = 3,
|
||||
|
||||
POINT_NONE = 0,
|
||||
POINT_CROW_FLIGHT = 1
|
||||
};
|
||||
|
||||
struct npc_gilnean_crow : public PassiveAI
|
||||
{
|
||||
npc_gilnean_crow(Creature* creature) : PassiveAI(creature) { }
|
||||
|
||||
void SpellHit(WorldObject* /*caster*/, SpellInfo const* spell) override
|
||||
{
|
||||
if (spell->Id == SPELL_GILNEAN_CROW)
|
||||
_events.ScheduleEvent(EVENT_APPLY_HOVER_BYTES, 500ms);
|
||||
}
|
||||
|
||||
void MovementInform(uint32 type, uint32 pointId) override
|
||||
{
|
||||
if (type == POINT_MOTION_TYPE && pointId == POINT_CROW_FLIGHT)
|
||||
{
|
||||
Position pos = me->GetRandomNearPosition(8.0f);
|
||||
pos.m_positionZ = me->GetPositionZ() + 10.0f;
|
||||
me->GetMotionMaster()->MovePoint(POINT_NONE, pos);
|
||||
me->DespawnOrUnsummon(14s);
|
||||
}
|
||||
}
|
||||
|
||||
void UpdateAI(uint32 diff) override
|
||||
{
|
||||
_events.Update(diff);
|
||||
|
||||
while (uint32 eventId = _events.ExecuteEvent())
|
||||
{
|
||||
switch (eventId)
|
||||
{
|
||||
case EVENT_APPLY_HOVER_BYTES:
|
||||
me->SetStandState(UNIT_STAND_STATE_SIT);
|
||||
me->SetAnimTier(AnimTier::Submerged);
|
||||
_events.ScheduleEvent(EVENT_FLY_AWAY_1, 1s);
|
||||
break;
|
||||
case EVENT_FLY_AWAY_1:
|
||||
{
|
||||
Position pos = me->GetRandomNearPosition(7.0f);
|
||||
pos.m_positionZ = me->GetPositionZ() + 8.0f;
|
||||
me->GetMotionMaster()->MovePoint(POINT_CROW_FLIGHT, pos);
|
||||
break;
|
||||
}
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
private:
|
||||
EventMap _events;
|
||||
};
|
||||
}
|
||||
|
||||
void AddSC_gilneas_chapter_1()
|
||||
{
|
||||
using namespace Gilneas::Chapter1;
|
||||
RegisterCreatureAI(npc_frightened_citizen);
|
||||
RegisterCreatureAI(npc_worgen_runt);
|
||||
RegisterCreatureAI(npc_josiah_avery);
|
||||
RegisterCreatureAI(npc_greymanes_horse);
|
||||
RegisterCreatureAI(npc_crowleys_horse);
|
||||
RegisterCreatureAI(npc_gilnean_crow);
|
||||
}
|
||||
+704
@@ -0,0 +1,704 @@
|
||||
/*
|
||||
* This file is part of the TrinityCore 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 "gilneas.h"
|
||||
#include "ScriptMgr.h"
|
||||
#include "CombatAI.h"
|
||||
#include "MotionMaster.h"
|
||||
#include "ObjectAccessor.h"
|
||||
#include "Player.h"
|
||||
#include "PassiveAI.h"
|
||||
#include "PhasingHandler.h"
|
||||
#include "ScriptedCreature.h"
|
||||
#include "SpellInfo.h"
|
||||
#include "SpellScript.h"
|
||||
#include "TemporarySummon.h"
|
||||
#include "Vehicle.h"
|
||||
|
||||
namespace Gilneas::Chapter1
|
||||
{
|
||||
|
||||
/*######
|
||||
## Quest 14098 - Evacuate the Merchant Square
|
||||
######*/
|
||||
|
||||
enum FrightenedCitizen
|
||||
{
|
||||
SAY_FRIGHTENED_CITIZEN_RESCUE = 0,
|
||||
|
||||
NPC_EVACUATION_STALKER_FIRST = 35830,
|
||||
NPC_EVACUATION_STALKER_NEAR = 35010,
|
||||
NPC_EVACUATION_STALKER_FAR = 35011,
|
||||
NPC_RAMPAGING_WORGEN = 35660,
|
||||
|
||||
CREDIT_35830 = 35830,
|
||||
|
||||
POINT_STALKER_FIRST = 1,
|
||||
POINT_STALKER_NEAR = 2,
|
||||
POINT_STALKER_FAR = 3,
|
||||
|
||||
EVENT_TALK_FRIGHTENED = 1,
|
||||
EVENT_MOVE_TO_NEAR_STALKER = 2,
|
||||
EVENT_MOVE_TO_FAR_STALKER = 3,
|
||||
EVENT_DESPAWN = 4
|
||||
};
|
||||
|
||||
struct npc_frightened_citizen : public PassiveAI
|
||||
{
|
||||
npc_frightened_citizen(Creature* creature) : PassiveAI(creature) { }
|
||||
|
||||
void IsSummonedBy(Unit* /*summoner*/) override
|
||||
{
|
||||
if (Creature* stalkerNear = me->FindNearestCreature(NPC_EVACUATION_STALKER_FIRST, 20.0f))
|
||||
me->GetMotionMaster()->MovePoint(POINT_STALKER_FIRST, stalkerNear->GetPosition(), true);
|
||||
}
|
||||
|
||||
void MovementInform(uint32 type, uint32 id) override
|
||||
{
|
||||
if (type != POINT_MOTION_TYPE)
|
||||
return;
|
||||
|
||||
switch (id)
|
||||
{
|
||||
case POINT_STALKER_FIRST:
|
||||
_events.ScheduleEvent(EVENT_TALK_FRIGHTENED, 1s);
|
||||
break;
|
||||
case POINT_STALKER_NEAR:
|
||||
_events.ScheduleEvent(EVENT_MOVE_TO_FAR_STALKER, 1ms);
|
||||
break;
|
||||
case POINT_STALKER_FAR:
|
||||
_events.ScheduleEvent(EVENT_DESPAWN, 1ms);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void UpdateAI(uint32 diff) override
|
||||
{
|
||||
_events.Update(diff);
|
||||
|
||||
while (uint32 eventId = _events.ExecuteEvent())
|
||||
{
|
||||
switch (eventId)
|
||||
{
|
||||
case EVENT_TALK_FRIGHTENED:
|
||||
if (Unit* summoner = me->ToTempSummon()->GetSummoner())
|
||||
{
|
||||
if (Player* player = summoner->ToPlayer())
|
||||
{
|
||||
player->KilledMonsterCredit(CREDIT_35830);
|
||||
Talk(SAY_FRIGHTENED_CITIZEN_RESCUE, summoner);
|
||||
}
|
||||
}
|
||||
_events.ScheduleEvent(EVENT_MOVE_TO_NEAR_STALKER, 2s);
|
||||
break;
|
||||
case EVENT_MOVE_TO_NEAR_STALKER:
|
||||
if (Creature* stalker = me->FindNearestCreature(NPC_EVACUATION_STALKER_FAR, 50.0f))
|
||||
me->GetMotionMaster()->MovePoint(POINT_STALKER_FAR, stalker->GetPosition(), true);
|
||||
else if (Creature* stalker = me->FindNearestCreature(NPC_EVACUATION_STALKER_NEAR, 100.0f))
|
||||
me->GetMotionMaster()->MovePoint(POINT_STALKER_NEAR, stalker->GetPosition(), true);
|
||||
break;
|
||||
case EVENT_MOVE_TO_FAR_STALKER:
|
||||
if (Creature* stalker = me->FindNearestCreature(NPC_EVACUATION_STALKER_FAR, 500.0f))
|
||||
me->GetMotionMaster()->MovePoint(POINT_STALKER_FAR, stalker->GetPosition(), true);
|
||||
break;
|
||||
case EVENT_DESPAWN:
|
||||
me->DespawnOrUnsummon();
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
private:
|
||||
EventMap _events;
|
||||
};
|
||||
|
||||
/*######
|
||||
## Quest 14154 - By the Skin of his Teeth
|
||||
######*/
|
||||
|
||||
enum WorgenRunt
|
||||
{
|
||||
EVENT_JUMP_TO_PRISON = 1,
|
||||
EVENT_AGGRO_PLAYER = 2,
|
||||
EVENT_FORCE_DESPAWN = 3,
|
||||
|
||||
PHASE_ROOF = 0,
|
||||
PHASE_COMBAT = 1,
|
||||
|
||||
WORGEN_ID_ROOF_1 = 0,
|
||||
WORGEN_ID_ROOF_2 = 1,
|
||||
WORGEN_ID_ROOF_3 = 2,
|
||||
WORGEN_ID_ROOF_4 = 3,
|
||||
WORGEN_ID_ROOF_5 = 4,
|
||||
WORGEN_ID_ROOF_6 = 5,
|
||||
WORGEN_ID_ROOF_7 = 6,
|
||||
|
||||
WORGEN_ID_CATHEDRAL_1 = 7,
|
||||
WORGEN_ID_CATHEDRAL_2 = 8,
|
||||
WORGEN_ID_CATHEDRAL_3 = 9,
|
||||
WORGEN_ID_CATHEDRAL_4 = 10,
|
||||
WORGEN_ID_CATHEDRAL_5 = 11,
|
||||
WORGEN_ID_CATHEDRAL_6 = 12,
|
||||
WORGEN_ID_CATHEDRAL_7 = 13,
|
||||
WORGEN_ID_CATHEDRAL_8 = 14,
|
||||
WORGEN_ID_CATHEDRAL_9 = 15,
|
||||
|
||||
NPC_WORGEN_RUNT_SPELL = 35188,
|
||||
};
|
||||
|
||||
static Position const RuntSpellSummonJumpPos = { -1671.915f, 1446.734f, 52.28712f };
|
||||
|
||||
struct npc_worgen_runt : public ScriptedAI
|
||||
{
|
||||
npc_worgen_runt(Creature* creature) : ScriptedAI(creature), _worgenID(0), _wayPointCounter(0), _jumped(false) { }
|
||||
|
||||
void JustEngagedWith(Unit* /*who*/) override
|
||||
{
|
||||
_events.SetPhase(PHASE_COMBAT);
|
||||
}
|
||||
|
||||
void IsSummonedBy(Unit* summoner) override
|
||||
{
|
||||
me->setActive(true); // we are in a phased and cut off map so we're fine to use that here
|
||||
_events.SetPhase(PHASE_ROOF);
|
||||
_events.ScheduleEvent(EVENT_FORCE_DESPAWN, 70s, 0, PHASE_ROOF);
|
||||
_playerGuid = summoner->GetGUID();
|
||||
if (me->GetEntry() == NPC_WORGEN_RUNT_SPELL)
|
||||
me->GetMotionMaster()->MoveJump(RuntSpellSummonJumpPos, 16.0f, 4.371286f);
|
||||
}
|
||||
|
||||
void DoAction(int32 action) override
|
||||
{
|
||||
_worgenID = action;
|
||||
switch (_worgenID) // ugly as fuck but needed for individual controls
|
||||
{
|
||||
// House roof cases
|
||||
case WORGEN_ID_ROOF_1:
|
||||
_wayPointCounter = runtHousePathSize1;
|
||||
me->GetMotionMaster()->MoveSmoothPath(runtHousePathSize1, worgenRuntHousePath1, runtHousePathSize1);
|
||||
break;
|
||||
case WORGEN_ID_ROOF_2:
|
||||
_wayPointCounter = runtHousePathSize2;
|
||||
me->GetMotionMaster()->MoveSmoothPath(runtHousePathSize2, worgenRuntHousePath2, runtHousePathSize2);
|
||||
break;
|
||||
case WORGEN_ID_ROOF_3:
|
||||
_wayPointCounter = runtHousePathSize3;
|
||||
me->GetMotionMaster()->MoveSmoothPath(runtHousePathSize3, worgenRuntHousePath3, runtHousePathSize3);
|
||||
break;
|
||||
case WORGEN_ID_ROOF_4:
|
||||
_wayPointCounter = runtHousePathSize4;
|
||||
me->GetMotionMaster()->MoveSmoothPath(runtHousePathSize4, worgenRuntHousePath4, runtHousePathSize4);
|
||||
break;
|
||||
case WORGEN_ID_ROOF_5:
|
||||
_wayPointCounter = runtHousePathSize5;
|
||||
me->GetMotionMaster()->MoveSmoothPath(runtHousePathSize5, worgenRuntHousePath5, runtHousePathSize5);
|
||||
break;
|
||||
case WORGEN_ID_ROOF_6:
|
||||
_wayPointCounter = runtHousePathSize6;
|
||||
me->GetMotionMaster()->MoveSmoothPath(runtHousePathSize6, worgenRuntHousePath6, runtHousePathSize6);
|
||||
break;
|
||||
case WORGEN_ID_ROOF_7:
|
||||
_wayPointCounter = runtHousePathSize7;
|
||||
me->GetMotionMaster()->MoveSmoothPath(runtHousePathSize7, worgenRuntHousePath7, runtHousePathSize7);
|
||||
break;
|
||||
// Cathedral cases
|
||||
case WORGEN_ID_CATHEDRAL_1:
|
||||
_wayPointCounter = runtCathedralPathSize1;
|
||||
me->GetMotionMaster()->MoveSmoothPath(runtCathedralPathSize1, worgenRuntCathedralPath1, runtCathedralPathSize1);
|
||||
break;
|
||||
case WORGEN_ID_CATHEDRAL_2:
|
||||
_wayPointCounter = runtCathedralPathSize2;
|
||||
me->GetMotionMaster()->MoveSmoothPath(runtCathedralPathSize2, worgenRuntCathedralPath1, runtCathedralPathSize2);
|
||||
break;
|
||||
case WORGEN_ID_CATHEDRAL_3:
|
||||
_wayPointCounter = runtCathedralPathSize3;
|
||||
me->GetMotionMaster()->MoveSmoothPath(runtCathedralPathSize3, worgenRuntCathedralPath1, runtCathedralPathSize3);
|
||||
break;
|
||||
case WORGEN_ID_CATHEDRAL_4:
|
||||
_wayPointCounter = runtCathedralPathSize4;
|
||||
me->GetMotionMaster()->MoveSmoothPath(runtCathedralPathSize4, worgenRuntCathedralPath1, runtCathedralPathSize4);
|
||||
break;
|
||||
case WORGEN_ID_CATHEDRAL_5:
|
||||
_wayPointCounter = runtCathedralPathSize5;
|
||||
me->GetMotionMaster()->MoveSmoothPath(runtCathedralPathSize5, worgenRuntCathedralPath1, runtCathedralPathSize5);
|
||||
break;
|
||||
case WORGEN_ID_CATHEDRAL_6:
|
||||
_wayPointCounter = runtCathedralPathSize6;
|
||||
me->GetMotionMaster()->MoveSmoothPath(runtCathedralPathSize6, worgenRuntCathedralPath1, runtCathedralPathSize6);
|
||||
break;
|
||||
case WORGEN_ID_CATHEDRAL_7:
|
||||
_wayPointCounter = runtCathedralPathSize7;
|
||||
me->GetMotionMaster()->MoveSmoothPath(runtCathedralPathSize7, worgenRuntCathedralPath1, runtCathedralPathSize7);
|
||||
break;
|
||||
case WORGEN_ID_CATHEDRAL_8:
|
||||
_wayPointCounter = runtCathedralPathSize8;
|
||||
me->GetMotionMaster()->MoveSmoothPath(runtCathedralPathSize8, worgenRuntCathedralPath1, runtCathedralPathSize8);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void JustDied(Unit* /*killer*/) override
|
||||
{
|
||||
me->DespawnOrUnsummon(5s);
|
||||
}
|
||||
|
||||
void MovementInform(uint32 type, uint32 pointId) override
|
||||
{
|
||||
if (type == EFFECT_MOTION_TYPE && pointId == _wayPointCounter && !_jumped)
|
||||
{
|
||||
_jumped = true;
|
||||
_events.ScheduleEvent(EVENT_JUMP_TO_PRISON, 1ms);
|
||||
}
|
||||
}
|
||||
|
||||
void UpdateAI(uint32 diff) override
|
||||
{
|
||||
if (!UpdateVictim() && !_events.IsInPhase(PHASE_ROOF))
|
||||
return;
|
||||
|
||||
_events.Update(diff);
|
||||
|
||||
while (uint32 eventId = _events.ExecuteEvent())
|
||||
{
|
||||
switch (eventId)
|
||||
{
|
||||
case EVENT_FORCE_DESPAWN:
|
||||
me->DespawnOrUnsummon();
|
||||
break;
|
||||
case EVENT_JUMP_TO_PRISON:
|
||||
me->GetMotionMaster()->MoveJump(worgenRuntJumpPos[_worgenID], 16.0f, _worgenID < WORGEN_ID_CATHEDRAL_1 ? 19.2911f : frand(3.945607f, 4.852813f));
|
||||
me->SetHomePosition(worgenRuntJumpPos[_worgenID]);
|
||||
_events.ScheduleEvent(EVENT_AGGRO_PLAYER, 2s);
|
||||
break;
|
||||
case EVENT_AGGRO_PLAYER:
|
||||
{
|
||||
Creature* crowley = me->FindNearestCreature(35077, 100.0f, true);
|
||||
Creature* tobias = me->FindNearestCreature(35124, 100.0f, true);
|
||||
|
||||
if (crowley && tobias)
|
||||
{
|
||||
if (me->GetDistance(crowley) <= me->GetDistance(tobias))
|
||||
AttackStart(crowley);
|
||||
else
|
||||
AttackStart(tobias);
|
||||
}
|
||||
else if (crowley)
|
||||
AttackStart(crowley);
|
||||
else if (tobias)
|
||||
AttackStart(tobias);
|
||||
|
||||
break;
|
||||
}
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
DoMeleeAttackIfReady();
|
||||
}
|
||||
private:
|
||||
uint32 _worgenID;
|
||||
uint32 _wayPointCounter;
|
||||
bool _jumped;
|
||||
ObjectGuid _playerGuid;
|
||||
EventMap _events;
|
||||
};
|
||||
|
||||
/*######
|
||||
## Quest 14159 - The Rebel Lord's Arsenal
|
||||
######*/
|
||||
|
||||
enum TheRebelLordsArsena
|
||||
{
|
||||
PHASE_ID_SUMMON = 170,
|
||||
PHASE_ID_WOUND = 171,
|
||||
NPC_LORNA_CROWLEY = 35378,
|
||||
NPC_GENERIC_TRIGGER_LAB = 35374,
|
||||
|
||||
SPELL_SHOOT_INSTAKILL = 67593,
|
||||
SPELL_COSMETIC_ATTACK = 42880,
|
||||
SPELL_PULL_TO = 67357,
|
||||
|
||||
EVENT_COSMETIC_ATTACK = 1,
|
||||
EVENT_JUMP_TO_PLAYER,
|
||||
EVENT_SHOOT_JOSIAH
|
||||
};
|
||||
|
||||
static Position const JosiahJumpPos = { -1796.63f, 1427.73f, 12.4624f };
|
||||
|
||||
struct npc_josiah_avery : public PassiveAI
|
||||
{
|
||||
npc_josiah_avery(Creature* creature) : PassiveAI(creature) { }
|
||||
|
||||
void IsSummonedBy(Unit* summoner) override
|
||||
{
|
||||
PhasingHandler::AddPhase(me, PHASE_ID_SUMMON, true);
|
||||
PhasingHandler::AddPhase(me, PHASE_ID_WOUND, true);
|
||||
_playerGuid = summoner->GetGUID();
|
||||
_events.ScheduleEvent(EVENT_COSMETIC_ATTACK, 500ms);
|
||||
}
|
||||
|
||||
void JustDied(Unit* /*killer*/) override
|
||||
{
|
||||
me->DespawnOrUnsummon(5s);
|
||||
}
|
||||
|
||||
void UpdateAI(uint32 diff) override
|
||||
{
|
||||
_events.Update(diff);
|
||||
|
||||
while (uint32 eventId = _events.ExecuteEvent())
|
||||
{
|
||||
switch (eventId)
|
||||
{
|
||||
case EVENT_COSMETIC_ATTACK:
|
||||
if (Player* player = ObjectAccessor::GetPlayer(*me, _playerGuid))
|
||||
{
|
||||
DoCast(player, SPELL_COSMETIC_ATTACK);
|
||||
if (Creature* lorna = me->FindNearestCreature(NPC_LORNA_CROWLEY, 30.0f, true))
|
||||
if (Creature* labTrigger = lorna->FindNearestCreature(NPC_GENERIC_TRIGGER_LAB, 5.0f, true))
|
||||
labTrigger->CastSpell(player, SPELL_PULL_TO);
|
||||
|
||||
_events.ScheduleEvent(EVENT_JUMP_TO_PLAYER, 1s);
|
||||
}
|
||||
break;
|
||||
case EVENT_JUMP_TO_PLAYER:
|
||||
me->GetMotionMaster()->MoveJump(JosiahJumpPos, 10.0f, 14.18636f);
|
||||
_events.ScheduleEvent(EVENT_SHOOT_JOSIAH, 500ms);
|
||||
break;
|
||||
case EVENT_SHOOT_JOSIAH:
|
||||
if (Creature* lorna = me->FindNearestCreature(NPC_LORNA_CROWLEY, 30.0f, true))
|
||||
lorna->CastSpell(me, SPELL_SHOOT_INSTAKILL, true);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
private:
|
||||
ObjectGuid _playerGuid;
|
||||
EventMap _events;
|
||||
};
|
||||
|
||||
/*######
|
||||
## Quest 14293 - Save Krennan Aranas
|
||||
######*/
|
||||
|
||||
static Position const GreymanesHorseJumpPos = { -1676.16f, 1346.19f, 15.1349f };
|
||||
|
||||
enum GreymanesHorse
|
||||
{
|
||||
SAY_ANNOUNCE_RESCUE = 0,
|
||||
SAY_RESCUED = 0,
|
||||
SAY_TRAPPED = 0,
|
||||
|
||||
PATH_KRENNAN_TREE = 0,
|
||||
PATH_KRENNAN_BACK = 1,
|
||||
|
||||
EVENT_START_PATH_1 = 1,
|
||||
EVENT_START_PATH_2,
|
||||
EVENT_JUMP_TO_KRENNAN,
|
||||
EVENT_ANNOUNCE_RESCUE,
|
||||
EVENT_DISMOUNT_PLAYER,
|
||||
|
||||
NPC_RESCUED_KRENNAN = 35907,
|
||||
NPC_TRAPPED_KRENNAN = 35753,
|
||||
};
|
||||
|
||||
struct npc_greymanes_horse : public VehicleAI
|
||||
{
|
||||
npc_greymanes_horse(Creature* creature) : VehicleAI(creature), _currentPath(PATH_KRENNAN_TREE) { }
|
||||
|
||||
void DamageTaken(Unit* /*attacker*/, uint32& damage) override
|
||||
{
|
||||
if (me->GetHealth() <= 1)
|
||||
damage = 0;
|
||||
else if (damage >= me->GetHealth())
|
||||
damage = me->GetHealth() - 1;
|
||||
}
|
||||
|
||||
void PassengerBoarded(Unit* passenger, int8 /*seatId*/, bool apply) override
|
||||
{
|
||||
if (apply && passenger->GetTypeId() == TYPEID_PLAYER)
|
||||
{
|
||||
me->SetControlled(true, UNIT_STATE_ROOT);
|
||||
_events.ScheduleEvent(EVENT_START_PATH_1, 1s);
|
||||
}
|
||||
else if (apply && passenger->GetEntry() == NPC_RESCUED_KRENNAN)
|
||||
_events.ScheduleEvent(EVENT_START_PATH_2, 1s);
|
||||
}
|
||||
|
||||
void MovementInform(uint32 type, uint32 pointId) override
|
||||
{
|
||||
if (type == EFFECT_MOTION_TYPE && pointId == pathSize1 && _currentPath == PATH_KRENNAN_TREE)
|
||||
_events.ScheduleEvent(EVENT_JUMP_TO_KRENNAN, 1ms);
|
||||
else if (type == EFFECT_MOTION_TYPE && pointId == pathSize2 && _currentPath == PATH_KRENNAN_BACK)
|
||||
_events.ScheduleEvent(EVENT_DISMOUNT_PLAYER, 1ms);
|
||||
}
|
||||
|
||||
void UpdateAI(uint32 diff) override
|
||||
{
|
||||
_events.Update(diff);
|
||||
|
||||
if (me->HasUnitState(UNIT_STATE_CASTING))
|
||||
return;
|
||||
|
||||
while (uint32 eventId = _events.ExecuteEvent())
|
||||
{
|
||||
switch (eventId)
|
||||
{
|
||||
case EVENT_START_PATH_1:
|
||||
me->SetControlled(false, UNIT_STATE_ROOT);
|
||||
_currentPath = PATH_KRENNAN_TREE;
|
||||
me->GetMotionMaster()->MoveSmoothPath(pathSize1, greymanesHorsePath1, pathSize1);
|
||||
break;
|
||||
case EVENT_JUMP_TO_KRENNAN:
|
||||
me->GetMotionMaster()->MoveJump(GreymanesHorseJumpPos, 16.0f, 14.00968f);
|
||||
_events.ScheduleEvent(EVENT_ANNOUNCE_RESCUE, 2s);
|
||||
break;
|
||||
case EVENT_ANNOUNCE_RESCUE:
|
||||
me->SetControlled(true, UNIT_STATE_ROOT);
|
||||
if (Unit* passenger = me->GetVehicleKit()->GetPassenger(0))
|
||||
{
|
||||
Talk(SAY_ANNOUNCE_RESCUE, passenger);
|
||||
if (Creature* krennan = me->FindNearestCreature(NPC_TRAPPED_KRENNAN, 30.0f, true))
|
||||
krennan->AI()->Talk(SAY_TRAPPED, passenger);
|
||||
}
|
||||
break;
|
||||
case EVENT_START_PATH_2:
|
||||
me->SetControlled(true, UNIT_STATE_ROOT);
|
||||
_currentPath = PATH_KRENNAN_BACK;
|
||||
me->GetMotionMaster()->MoveSmoothPath(pathSize2, greymanesHorsePath2, pathSize2);
|
||||
break;
|
||||
case EVENT_DISMOUNT_PLAYER:
|
||||
if (Unit* passenger = me->GetVehicleKit()->GetPassenger(1))
|
||||
{
|
||||
if (Unit* player = me->GetVehicleKit()->GetPassenger(0))
|
||||
{
|
||||
if (Creature* krennan = passenger->ToCreature())
|
||||
{
|
||||
std::vector<Unit*> storedAttackers;
|
||||
|
||||
for (Unit* attacker : me->getAttackers())
|
||||
storedAttackers.push_back(attacker);
|
||||
|
||||
for (Unit* attacker : storedAttackers)
|
||||
{
|
||||
if (Creature* creature = attacker->ToCreature())
|
||||
if (creature->IsAIEnabled())
|
||||
creature->AI()->EnterEvadeMode();
|
||||
}
|
||||
|
||||
player->ExitVehicle();
|
||||
krennan->AddUnitState(UNIT_STATE_ROOT);
|
||||
krennan->ExitVehicle();
|
||||
krennan->AI()->Talk(SAY_RESCUED, me);
|
||||
krennan->DespawnOrUnsummon(7s);
|
||||
}
|
||||
}
|
||||
}
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
private:
|
||||
EventMap _events;
|
||||
uint32 _currentPath;
|
||||
};
|
||||
|
||||
/*######
|
||||
## Quest 14212 - Sacrifices
|
||||
######*/
|
||||
|
||||
enum CrowleysHorse
|
||||
{
|
||||
EVENT_JUMP_OVER_BARRICADES_1 = 1,
|
||||
EVENT_JUMP_OVER_BARRICADES_2,
|
||||
EVENT_MOVE_PATH_MAIN_1,
|
||||
EVENT_MOVE_PATH_MAIN_2,
|
||||
EVENT_MOVE_OFF_PATH = 6,
|
||||
|
||||
PATH_ID_CROWLEYS_HORSE_1 = 352310,
|
||||
PATH_ID_CROWLEYS_HORSE_2 = 352311,
|
||||
PATH_ID_CROWLEYS_HORSE_3 = 444280,
|
||||
|
||||
SPELL_THROW_TORCH = 67063,
|
||||
NPC_CROWLEYS_HORSE_2 = 44428,
|
||||
};
|
||||
|
||||
static Position const CrowleysHorseJumpPos = { -1714.762f, 1673.16f, 20.49182f };
|
||||
static Position const CrowleysHorseJumpPos2 = { -1566.71f, 1708.04f, 20.4849f };
|
||||
|
||||
struct npc_crowleys_horse : public VehicleAI
|
||||
{
|
||||
npc_crowleys_horse(Creature* creature) : VehicleAI(creature), _currentPath(0) { }
|
||||
|
||||
void PassengerBoarded(Unit* passenger, int8 /*seatId*/, bool apply) override
|
||||
{
|
||||
if (apply && passenger->GetTypeId() == TYPEID_PLAYER && me->GetEntry() != NPC_CROWLEYS_HORSE_2)
|
||||
{
|
||||
me->SetControlled(true, UNIT_STATE_ROOT);
|
||||
_events.ScheduleEvent(EVENT_JUMP_OVER_BARRICADES_1, 2s);
|
||||
}
|
||||
else if (apply && passenger->GetTypeId() == TYPEID_PLAYER)
|
||||
{
|
||||
me->SetControlled(true, UNIT_STATE_ROOT);
|
||||
_events.ScheduleEvent(EVENT_MOVE_OFF_PATH, 2s);
|
||||
}
|
||||
}
|
||||
|
||||
void MovementInform(uint32 type, uint32 pointId) override
|
||||
{
|
||||
if (type == WAYPOINT_MOTION_TYPE && pointId == 14 && _currentPath == PATH_ID_CROWLEYS_HORSE_1)
|
||||
_events.ScheduleEvent(EVENT_JUMP_OVER_BARRICADES_2, 1s);
|
||||
else if (type == WAYPOINT_MOTION_TYPE && pointId == 15 && _currentPath == PATH_ID_CROWLEYS_HORSE_2)
|
||||
_events.ScheduleEvent(EVENT_DISMOUNT_PLAYER, 1ms);
|
||||
else if (type == WAYPOINT_MOTION_TYPE && pointId == 16 && _currentPath == PATH_ID_CROWLEYS_HORSE_3)
|
||||
_events.ScheduleEvent(EVENT_DISMOUNT_PLAYER, 1ms);
|
||||
}
|
||||
|
||||
void UpdateAI(uint32 diff) override
|
||||
{
|
||||
_events.Update(diff);
|
||||
|
||||
while (uint32 eventId = _events.ExecuteEvent())
|
||||
{
|
||||
switch (eventId)
|
||||
{
|
||||
case EVENT_JUMP_OVER_BARRICADES_1:
|
||||
me->SetControlled(false, UNIT_STATE_ROOT);
|
||||
me->GetMotionMaster()->MoveJump(CrowleysHorseJumpPos, 16.0f, 18.56182f);
|
||||
_events.ScheduleEvent(EVENT_MOVE_PATH_MAIN_1, 4s);
|
||||
break;
|
||||
case EVENT_MOVE_PATH_MAIN_1:
|
||||
me->GetMotionMaster()->MovePath(PATH_ID_CROWLEYS_HORSE_1, false);
|
||||
_currentPath = PATH_ID_CROWLEYS_HORSE_1;
|
||||
break;
|
||||
case EVENT_JUMP_OVER_BARRICADES_2:
|
||||
me->GetMotionMaster()->MoveJump(CrowleysHorseJumpPos2, 16.0f, 18.56182f);
|
||||
_events.ScheduleEvent(EVENT_MOVE_PATH_MAIN_2, 2s);
|
||||
break;
|
||||
case EVENT_MOVE_PATH_MAIN_2:
|
||||
_currentPath = PATH_ID_CROWLEYS_HORSE_2;
|
||||
me->GetMotionMaster()->MovePath(PATH_ID_CROWLEYS_HORSE_2, false);
|
||||
break;
|
||||
case EVENT_DISMOUNT_PLAYER:
|
||||
{
|
||||
std::set<Unit*> attackersCopy = me->getAttackers();
|
||||
for (Unit* attacker : attackersCopy)
|
||||
{
|
||||
if (Creature* creature = attacker->ToCreature())
|
||||
if (creature->IsAIEnabled())
|
||||
creature->AI()->EnterEvadeMode();
|
||||
}
|
||||
|
||||
me->RemoveAurasDueToSpell(VEHICLE_SPELL_RIDE_HARDCODED);
|
||||
me->DespawnOrUnsummon(5s);
|
||||
break;
|
||||
}
|
||||
case EVENT_MOVE_OFF_PATH:
|
||||
me->SetControlled(false, UNIT_STATE_ROOT);
|
||||
_currentPath = PATH_ID_CROWLEYS_HORSE_3;
|
||||
me->GetMotionMaster()->MovePath(PATH_ID_CROWLEYS_HORSE_3, false);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
private:
|
||||
EventMap _events;
|
||||
uint32 _currentPath;
|
||||
};
|
||||
|
||||
enum GileanCrow
|
||||
{
|
||||
SPELL_GILNEAN_CROW = 93275,
|
||||
EVENT_APPLY_HOVER_BYTES = 1,
|
||||
EVENT_FLY_AWAY_1 = 2,
|
||||
EVENT_FLY_AWAY_2 = 3,
|
||||
|
||||
POINT_NONE = 0,
|
||||
POINT_CROW_FLIGHT = 1
|
||||
};
|
||||
|
||||
struct npc_gilnean_crow : public PassiveAI
|
||||
{
|
||||
npc_gilnean_crow(Creature* creature) : PassiveAI(creature) { }
|
||||
|
||||
void SpellHit(WorldObject* /*caster*/, SpellInfo const* spell) override
|
||||
{
|
||||
if (spell->Id == SPELL_GILNEAN_CROW)
|
||||
_events.ScheduleEvent(EVENT_APPLY_HOVER_BYTES, 500ms);
|
||||
}
|
||||
|
||||
void MovementInform(uint32 type, uint32 pointId) override
|
||||
{
|
||||
if (type == POINT_MOTION_TYPE && pointId == POINT_CROW_FLIGHT)
|
||||
{
|
||||
Position pos = me->GetRandomNearPosition(8.0f);
|
||||
pos.m_positionZ = me->GetPositionZ() + 10.0f;
|
||||
me->GetMotionMaster()->MovePoint(POINT_NONE, pos);
|
||||
me->DespawnOrUnsummon(14s);
|
||||
}
|
||||
}
|
||||
|
||||
void UpdateAI(uint32 diff) override
|
||||
{
|
||||
_events.Update(diff);
|
||||
|
||||
while (uint32 eventId = _events.ExecuteEvent())
|
||||
{
|
||||
switch (eventId)
|
||||
{
|
||||
case EVENT_APPLY_HOVER_BYTES:
|
||||
me->SetStandState(UNIT_STAND_STATE_SIT);
|
||||
me->SetAnimTier(AnimTier::Submerged);
|
||||
_events.ScheduleEvent(EVENT_FLY_AWAY_1, 1s);
|
||||
break;
|
||||
case EVENT_FLY_AWAY_1:
|
||||
{
|
||||
Position pos = me->GetRandomNearPosition(7.0f);
|
||||
pos.m_positionZ = me->GetPositionZ() + 8.0f;
|
||||
me->GetMotionMaster()->MovePoint(POINT_CROW_FLIGHT, pos);
|
||||
break;
|
||||
}
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
private:
|
||||
EventMap _events;
|
||||
};
|
||||
}
|
||||
|
||||
void AddSC_gilneas_chapter_1()
|
||||
{
|
||||
using namespace Gilneas::Chapter1;
|
||||
RegisterCreatureAI(npc_frightened_citizen);
|
||||
RegisterCreatureAI(npc_worgen_runt);
|
||||
RegisterCreatureAI(npc_josiah_avery);
|
||||
RegisterCreatureAI(npc_greymanes_horse);
|
||||
RegisterCreatureAI(npc_crowleys_horse);
|
||||
RegisterCreatureAI(npc_gilnean_crow);
|
||||
}
|
||||
+704
@@ -0,0 +1,704 @@
|
||||
/*
|
||||
* This file is part of the TrinityCore 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 "gilneas.h"
|
||||
#include "ScriptMgr.h"
|
||||
#include "CombatAI.h"
|
||||
#include "MotionMaster.h"
|
||||
#include "ObjectAccessor.h"
|
||||
#include "Player.h"
|
||||
#include "PassiveAI.h"
|
||||
#include "PhasingHandler.h"
|
||||
#include "ScriptedCreature.h"
|
||||
#include "SpellInfo.h"
|
||||
#include "SpellScript.h"
|
||||
#include "TemporarySummon.h"
|
||||
#include "Vehicle.h"
|
||||
|
||||
namespace Gilneas::Chapter1
|
||||
{
|
||||
|
||||
/*######
|
||||
## Quest 14098 - Evacuate the Merchant Square
|
||||
######*/
|
||||
|
||||
enum FrightenedCitizen
|
||||
{
|
||||
SAY_FRIGHTENED_CITIZEN_RESCUE = 0,
|
||||
|
||||
NPC_EVACUATION_STALKER_FIRST = 35830,
|
||||
NPC_EVACUATION_STALKER_NEAR = 35010,
|
||||
NPC_EVACUATION_STALKER_FAR = 35011,
|
||||
NPC_RAMPAGING_WORGEN = 35660,
|
||||
|
||||
CREDIT_35830 = 35830,
|
||||
|
||||
POINT_STALKER_FIRST = 1,
|
||||
POINT_STALKER_NEAR = 2,
|
||||
POINT_STALKER_FAR = 3,
|
||||
|
||||
EVENT_TALK_FRIGHTENED = 1,
|
||||
EVENT_MOVE_TO_NEAR_STALKER = 2,
|
||||
EVENT_MOVE_TO_FAR_STALKER = 3,
|
||||
EVENT_DESPAWN = 4
|
||||
};
|
||||
|
||||
struct npc_frightened_citizen : public PassiveAI
|
||||
{
|
||||
npc_frightened_citizen(Creature* creature) : PassiveAI(creature) { }
|
||||
|
||||
void IsSummonedBy(Unit* /*summoner*/) override
|
||||
{
|
||||
if (Creature* stalkerNear = me->FindNearestCreature(NPC_EVACUATION_STALKER_FIRST, 20.0f))
|
||||
me->GetMotionMaster()->MovePoint(POINT_STALKER_FIRST, stalkerNear->GetPosition(), true);
|
||||
}
|
||||
|
||||
void MovementInform(uint32 type, uint32 id) override
|
||||
{
|
||||
if (type != POINT_MOTION_TYPE)
|
||||
return;
|
||||
|
||||
switch (id)
|
||||
{
|
||||
case POINT_STALKER_FIRST:
|
||||
_events.ScheduleEvent(EVENT_TALK_FRIGHTENED, 1s);
|
||||
break;
|
||||
case POINT_STALKER_NEAR:
|
||||
_events.ScheduleEvent(EVENT_MOVE_TO_FAR_STALKER, 1ms);
|
||||
break;
|
||||
case POINT_STALKER_FAR:
|
||||
_events.ScheduleEvent(EVENT_DESPAWN, 1ms);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void UpdateAI(uint32 diff) override
|
||||
{
|
||||
_events.Update(diff);
|
||||
|
||||
while (uint32 eventId = _events.ExecuteEvent())
|
||||
{
|
||||
switch (eventId)
|
||||
{
|
||||
case EVENT_TALK_FRIGHTENED:
|
||||
if (Unit* summoner = me->ToTempSummon()->GetSummoner())
|
||||
{
|
||||
if (Player* player = summoner->ToPlayer())
|
||||
{
|
||||
player->KilledMonsterCredit(CREDIT_35830);
|
||||
Talk(SAY_FRIGHTENED_CITIZEN_RESCUE, summoner);
|
||||
}
|
||||
}
|
||||
_events.ScheduleEvent(EVENT_MOVE_TO_NEAR_STALKER, 2s);
|
||||
break;
|
||||
case EVENT_MOVE_TO_NEAR_STALKER:
|
||||
if (Creature* stalker = me->FindNearestCreature(NPC_EVACUATION_STALKER_FAR, 50.0f))
|
||||
me->GetMotionMaster()->MovePoint(POINT_STALKER_FAR, stalker->GetPosition(), true);
|
||||
else if (Creature* stalker = me->FindNearestCreature(NPC_EVACUATION_STALKER_NEAR, 100.0f))
|
||||
me->GetMotionMaster()->MovePoint(POINT_STALKER_NEAR, stalker->GetPosition(), true);
|
||||
break;
|
||||
case EVENT_MOVE_TO_FAR_STALKER:
|
||||
if (Creature* stalker = me->FindNearestCreature(NPC_EVACUATION_STALKER_FAR, 500.0f))
|
||||
me->GetMotionMaster()->MovePoint(POINT_STALKER_FAR, stalker->GetPosition(), true);
|
||||
break;
|
||||
case EVENT_DESPAWN:
|
||||
me->DespawnOrUnsummon();
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
private:
|
||||
EventMap _events;
|
||||
};
|
||||
|
||||
/*######
|
||||
## Quest 14154 - By the Skin of his Teeth
|
||||
######*/
|
||||
|
||||
enum WorgenRunt
|
||||
{
|
||||
EVENT_JUMP_TO_PRISON = 1,
|
||||
EVENT_AGGRO_PLAYER = 2,
|
||||
EVENT_FORCE_DESPAWN = 3,
|
||||
|
||||
PHASE_ROOF = 0,
|
||||
PHASE_COMBAT = 1,
|
||||
|
||||
WORGEN_ID_ROOF_1 = 0,
|
||||
WORGEN_ID_ROOF_2 = 1,
|
||||
WORGEN_ID_ROOF_3 = 2,
|
||||
WORGEN_ID_ROOF_4 = 3,
|
||||
WORGEN_ID_ROOF_5 = 4,
|
||||
WORGEN_ID_ROOF_6 = 5,
|
||||
WORGEN_ID_ROOF_7 = 6,
|
||||
|
||||
WORGEN_ID_CATHEDRAL_1 = 7,
|
||||
WORGEN_ID_CATHEDRAL_2 = 8,
|
||||
WORGEN_ID_CATHEDRAL_3 = 9,
|
||||
WORGEN_ID_CATHEDRAL_4 = 10,
|
||||
WORGEN_ID_CATHEDRAL_5 = 11,
|
||||
WORGEN_ID_CATHEDRAL_6 = 12,
|
||||
WORGEN_ID_CATHEDRAL_7 = 13,
|
||||
WORGEN_ID_CATHEDRAL_8 = 14,
|
||||
WORGEN_ID_CATHEDRAL_9 = 15,
|
||||
|
||||
NPC_WORGEN_RUNT_SPELL = 35188,
|
||||
};
|
||||
|
||||
static Position const RuntSpellSummonJumpPos = { -1671.915f, 1446.734f, 52.28712f };
|
||||
|
||||
struct npc_worgen_runt : public ScriptedAI
|
||||
{
|
||||
npc_worgen_runt(Creature* creature) : ScriptedAI(creature), _worgenID(0), _wayPointCounter(0), _jumped(false) { }
|
||||
|
||||
void JustEngagedWith(Unit* /*who*/) override
|
||||
{
|
||||
_events.SetPhase(PHASE_COMBAT);
|
||||
}
|
||||
|
||||
void IsSummonedBy(Unit* summoner) override
|
||||
{
|
||||
me->setActive(true); // we are in a phased and cut off map so we're fine to use that here
|
||||
_events.SetPhase(PHASE_ROOF);
|
||||
_events.ScheduleEvent(EVENT_FORCE_DESPAWN, 70s, 0, PHASE_ROOF);
|
||||
_playerGuid = summoner->GetGUID();
|
||||
if (me->GetEntry() == NPC_WORGEN_RUNT_SPELL)
|
||||
me->GetMotionMaster()->MoveJump(RuntSpellSummonJumpPos, 16.0f, 4.371286f);
|
||||
}
|
||||
|
||||
void DoAction(int32 action) override
|
||||
{
|
||||
_worgenID = action;
|
||||
switch (_worgenID) // ugly as fuck but needed for individual controls
|
||||
{
|
||||
// House roof cases
|
||||
case WORGEN_ID_ROOF_1:
|
||||
_wayPointCounter = runtHousePathSize1;
|
||||
me->GetMotionMaster()->MoveSmoothPath(runtHousePathSize1, worgenRuntHousePath1, runtHousePathSize1);
|
||||
break;
|
||||
case WORGEN_ID_ROOF_2:
|
||||
_wayPointCounter = runtHousePathSize2;
|
||||
me->GetMotionMaster()->MoveSmoothPath(runtHousePathSize2, worgenRuntHousePath2, runtHousePathSize2);
|
||||
break;
|
||||
case WORGEN_ID_ROOF_3:
|
||||
_wayPointCounter = runtHousePathSize3;
|
||||
me->GetMotionMaster()->MoveSmoothPath(runtHousePathSize3, worgenRuntHousePath3, runtHousePathSize3);
|
||||
break;
|
||||
case WORGEN_ID_ROOF_4:
|
||||
_wayPointCounter = runtHousePathSize4;
|
||||
me->GetMotionMaster()->MoveSmoothPath(runtHousePathSize4, worgenRuntHousePath4, runtHousePathSize4);
|
||||
break;
|
||||
case WORGEN_ID_ROOF_5:
|
||||
_wayPointCounter = runtHousePathSize5;
|
||||
me->GetMotionMaster()->MoveSmoothPath(runtHousePathSize5, worgenRuntHousePath5, runtHousePathSize5);
|
||||
break;
|
||||
case WORGEN_ID_ROOF_6:
|
||||
_wayPointCounter = runtHousePathSize6;
|
||||
me->GetMotionMaster()->MoveSmoothPath(runtHousePathSize6, worgenRuntHousePath6, runtHousePathSize6);
|
||||
break;
|
||||
case WORGEN_ID_ROOF_7:
|
||||
_wayPointCounter = runtHousePathSize7;
|
||||
me->GetMotionMaster()->MoveSmoothPath(runtHousePathSize7, worgenRuntHousePath7, runtHousePathSize7);
|
||||
break;
|
||||
// Cathedral cases
|
||||
case WORGEN_ID_CATHEDRAL_1:
|
||||
_wayPointCounter = runtCathedralPathSize1;
|
||||
me->GetMotionMaster()->MoveSmoothPath(runtCathedralPathSize1, worgenRuntCathedralPath1, runtCathedralPathSize1);
|
||||
break;
|
||||
case WORGEN_ID_CATHEDRAL_2:
|
||||
_wayPointCounter = runtCathedralPathSize2;
|
||||
me->GetMotionMaster()->MoveSmoothPath(runtCathedralPathSize2, worgenRuntCathedralPath1, runtCathedralPathSize2);
|
||||
break;
|
||||
case WORGEN_ID_CATHEDRAL_3:
|
||||
_wayPointCounter = runtCathedralPathSize3;
|
||||
me->GetMotionMaster()->MoveSmoothPath(runtCathedralPathSize3, worgenRuntCathedralPath1, runtCathedralPathSize3);
|
||||
break;
|
||||
case WORGEN_ID_CATHEDRAL_4:
|
||||
_wayPointCounter = runtCathedralPathSize4;
|
||||
me->GetMotionMaster()->MoveSmoothPath(runtCathedralPathSize4, worgenRuntCathedralPath1, runtCathedralPathSize4);
|
||||
break;
|
||||
case WORGEN_ID_CATHEDRAL_5:
|
||||
_wayPointCounter = runtCathedralPathSize5;
|
||||
me->GetMotionMaster()->MoveSmoothPath(runtCathedralPathSize5, worgenRuntCathedralPath1, runtCathedralPathSize5);
|
||||
break;
|
||||
case WORGEN_ID_CATHEDRAL_6:
|
||||
_wayPointCounter = runtCathedralPathSize6;
|
||||
me->GetMotionMaster()->MoveSmoothPath(runtCathedralPathSize6, worgenRuntCathedralPath1, runtCathedralPathSize6);
|
||||
break;
|
||||
case WORGEN_ID_CATHEDRAL_7:
|
||||
_wayPointCounter = runtCathedralPathSize7;
|
||||
me->GetMotionMaster()->MoveSmoothPath(runtCathedralPathSize7, worgenRuntCathedralPath1, runtCathedralPathSize7);
|
||||
break;
|
||||
case WORGEN_ID_CATHEDRAL_8:
|
||||
_wayPointCounter = runtCathedralPathSize8;
|
||||
me->GetMotionMaster()->MoveSmoothPath(runtCathedralPathSize8, worgenRuntCathedralPath1, runtCathedralPathSize8);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void JustDied(Unit* /*killer*/) override
|
||||
{
|
||||
me->DespawnOrUnsummon(5s);
|
||||
}
|
||||
|
||||
void MovementInform(uint32 type, uint32 pointId) override
|
||||
{
|
||||
if (type == EFFECT_MOTION_TYPE && pointId == _wayPointCounter && !_jumped)
|
||||
{
|
||||
_jumped = true;
|
||||
_events.ScheduleEvent(EVENT_JUMP_TO_PRISON, 1ms);
|
||||
}
|
||||
}
|
||||
|
||||
void UpdateAI(uint32 diff) override
|
||||
{
|
||||
if (!UpdateVictim() && !_events.IsInPhase(PHASE_ROOF))
|
||||
return;
|
||||
|
||||
_events.Update(diff);
|
||||
|
||||
while (uint32 eventId = _events.ExecuteEvent())
|
||||
{
|
||||
switch (eventId)
|
||||
{
|
||||
case EVENT_FORCE_DESPAWN:
|
||||
me->DespawnOrUnsummon();
|
||||
break;
|
||||
case EVENT_JUMP_TO_PRISON:
|
||||
me->GetMotionMaster()->MoveJump(worgenRuntJumpPos[_worgenID], 16.0f, _worgenID < WORGEN_ID_CATHEDRAL_1 ? 19.2911f : frand(3.945607f, 4.852813f));
|
||||
me->SetHomePosition(worgenRuntJumpPos[_worgenID]);
|
||||
_events.ScheduleEvent(EVENT_AGGRO_PLAYER, 2s);
|
||||
break;
|
||||
case EVENT_AGGRO_PLAYER:
|
||||
{
|
||||
Creature* crowley = me->FindNearestCreature(35077, 100.0f, true);
|
||||
Creature* tobias = me->FindNearestCreature(35124, 100.0f, true);
|
||||
|
||||
if (crowley && tobias)
|
||||
{
|
||||
if (me->GetDistance(crowley) <= me->GetDistance(tobias))
|
||||
AttackStart(crowley);
|
||||
else
|
||||
AttackStart(tobias);
|
||||
}
|
||||
else if (crowley)
|
||||
AttackStart(crowley);
|
||||
else if (tobias)
|
||||
AttackStart(tobias);
|
||||
|
||||
break;
|
||||
}
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
DoMeleeAttackIfReady();
|
||||
}
|
||||
private:
|
||||
uint32 _worgenID;
|
||||
uint32 _wayPointCounter;
|
||||
bool _jumped;
|
||||
ObjectGuid _playerGuid;
|
||||
EventMap _events;
|
||||
};
|
||||
|
||||
/*######
|
||||
## Quest 14159 - The Rebel Lord's Arsenal
|
||||
######*/
|
||||
|
||||
enum TheRebelLordsArsena
|
||||
{
|
||||
PHASE_ID_SUMMON = 170,
|
||||
PHASE_ID_WOUND = 171,
|
||||
NPC_LORNA_CROWLEY = 35378,
|
||||
NPC_GENERIC_TRIGGER_LAB = 35374,
|
||||
|
||||
SPELL_SHOOT_INSTAKILL = 67593,
|
||||
SPELL_COSMETIC_ATTACK = 42880,
|
||||
SPELL_PULL_TO = 67357,
|
||||
|
||||
EVENT_COSMETIC_ATTACK = 1,
|
||||
EVENT_JUMP_TO_PLAYER,
|
||||
EVENT_SHOOT_JOSIAH
|
||||
};
|
||||
|
||||
static Position const JosiahJumpPos = { -1796.63f, 1427.73f, 12.4624f };
|
||||
|
||||
struct npc_josiah_avery : public PassiveAI
|
||||
{
|
||||
npc_josiah_avery(Creature* creature) : PassiveAI(creature) { }
|
||||
|
||||
void IsSummonedBy(Unit* summoner) override
|
||||
{
|
||||
PhasingHandler::AddPhase(me, PHASE_ID_SUMMON, true);
|
||||
PhasingHandler::AddPhase(me, PHASE_ID_WOUND, true);
|
||||
_playerGuid = summoner->GetGUID();
|
||||
_events.ScheduleEvent(EVENT_COSMETIC_ATTACK, 500ms);
|
||||
}
|
||||
|
||||
void JustDied(Unit* /*killer*/) override
|
||||
{
|
||||
me->DespawnOrUnsummon(5s);
|
||||
}
|
||||
|
||||
void UpdateAI(uint32 diff) override
|
||||
{
|
||||
_events.Update(diff);
|
||||
|
||||
while (uint32 eventId = _events.ExecuteEvent())
|
||||
{
|
||||
switch (eventId)
|
||||
{
|
||||
case EVENT_COSMETIC_ATTACK:
|
||||
if (Player* player = ObjectAccessor::GetPlayer(*me, _playerGuid))
|
||||
{
|
||||
DoCast(player, SPELL_COSMETIC_ATTACK);
|
||||
if (Creature* lorna = me->FindNearestCreature(NPC_LORNA_CROWLEY, 30.0f, true))
|
||||
if (Creature* labTrigger = lorna->FindNearestCreature(NPC_GENERIC_TRIGGER_LAB, 5.0f, true))
|
||||
labTrigger->CastSpell(player, SPELL_PULL_TO);
|
||||
|
||||
_events.ScheduleEvent(EVENT_JUMP_TO_PLAYER, 1s);
|
||||
}
|
||||
break;
|
||||
case EVENT_JUMP_TO_PLAYER:
|
||||
me->GetMotionMaster()->MoveJump(JosiahJumpPos, 10.0f, 14.18636f);
|
||||
_events.ScheduleEvent(EVENT_SHOOT_JOSIAH, 500ms);
|
||||
break;
|
||||
case EVENT_SHOOT_JOSIAH:
|
||||
if (Creature* lorna = me->FindNearestCreature(NPC_LORNA_CROWLEY, 30.0f, true))
|
||||
lorna->CastSpell(me, SPELL_SHOOT_INSTAKILL, true);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
private:
|
||||
ObjectGuid _playerGuid;
|
||||
EventMap _events;
|
||||
};
|
||||
|
||||
/*######
|
||||
## Quest 14293 - Save Krennan Aranas
|
||||
######*/
|
||||
|
||||
static Position const GreymanesHorseJumpPos = { -1676.16f, 1346.19f, 15.1349f };
|
||||
|
||||
enum GreymanesHorse
|
||||
{
|
||||
SAY_ANNOUNCE_RESCUE = 0,
|
||||
SAY_RESCUED = 0,
|
||||
SAY_TRAPPED = 0,
|
||||
|
||||
PATH_KRENNAN_TREE = 0,
|
||||
PATH_KRENNAN_BACK = 1,
|
||||
|
||||
EVENT_START_PATH_1 = 1,
|
||||
EVENT_START_PATH_2,
|
||||
EVENT_JUMP_TO_KRENNAN,
|
||||
EVENT_ANNOUNCE_RESCUE,
|
||||
EVENT_DISMOUNT_PLAYER,
|
||||
|
||||
NPC_RESCUED_KRENNAN = 35907,
|
||||
NPC_TRAPPED_KRENNAN = 35753,
|
||||
};
|
||||
|
||||
struct npc_greymanes_horse : public VehicleAI
|
||||
{
|
||||
npc_greymanes_horse(Creature* creature) : VehicleAI(creature), _currentPath(PATH_KRENNAN_TREE) { }
|
||||
|
||||
void DamageTaken(Unit* /*attacker*/, uint32& damage) override
|
||||
{
|
||||
if (me->GetHealth() <= 1)
|
||||
damage = 0;
|
||||
else if (damage >= me->GetHealth())
|
||||
damage = me->GetHealth() - 1;
|
||||
}
|
||||
|
||||
void PassengerBoarded(Unit* passenger, int8 /*seatId*/, bool apply) override
|
||||
{
|
||||
if (apply && passenger->GetTypeId() == TYPEID_PLAYER)
|
||||
{
|
||||
me->SetControlled(true, UNIT_STATE_ROOT);
|
||||
_events.ScheduleEvent(EVENT_START_PATH_1, 1s);
|
||||
}
|
||||
else if (apply && passenger->GetEntry() == NPC_RESCUED_KRENNAN)
|
||||
_events.ScheduleEvent(EVENT_START_PATH_2, 1s);
|
||||
}
|
||||
|
||||
void MovementInform(uint32 type, uint32 pointId) override
|
||||
{
|
||||
if (type == EFFECT_MOTION_TYPE && pointId == pathSize1 && _currentPath == PATH_KRENNAN_TREE)
|
||||
_events.ScheduleEvent(EVENT_JUMP_TO_KRENNAN, 1ms);
|
||||
else if (type == EFFECT_MOTION_TYPE && pointId == pathSize2 && _currentPath == PATH_KRENNAN_BACK)
|
||||
_events.ScheduleEvent(EVENT_DISMOUNT_PLAYER, 1ms);
|
||||
}
|
||||
|
||||
void UpdateAI(uint32 diff) override
|
||||
{
|
||||
_events.Update(diff);
|
||||
|
||||
if (me->HasUnitState(UNIT_STATE_CASTING))
|
||||
return;
|
||||
|
||||
while (uint32 eventId = _events.ExecuteEvent())
|
||||
{
|
||||
switch (eventId)
|
||||
{
|
||||
case EVENT_START_PATH_1:
|
||||
me->SetControlled(false, UNIT_STATE_ROOT);
|
||||
_currentPath = PATH_KRENNAN_TREE;
|
||||
me->GetMotionMaster()->MoveSmoothPath(pathSize1, greymanesHorsePath1, pathSize1);
|
||||
break;
|
||||
case EVENT_JUMP_TO_KRENNAN:
|
||||
me->GetMotionMaster()->MoveJump(GreymanesHorseJumpPos, 16.0f, 14.00968f);
|
||||
_events.ScheduleEvent(EVENT_ANNOUNCE_RESCUE, 2s);
|
||||
break;
|
||||
case EVENT_ANNOUNCE_RESCUE:
|
||||
me->SetControlled(true, UNIT_STATE_ROOT);
|
||||
if (Unit* passenger = me->GetVehicleKit()->GetPassenger(0))
|
||||
{
|
||||
Talk(SAY_ANNOUNCE_RESCUE, passenger);
|
||||
if (Creature* krennan = me->FindNearestCreature(NPC_TRAPPED_KRENNAN, 30.0f, true))
|
||||
krennan->AI()->Talk(SAY_TRAPPED, passenger);
|
||||
}
|
||||
break;
|
||||
case EVENT_START_PATH_2:
|
||||
me->SetControlled(true, UNIT_STATE_ROOT);
|
||||
_currentPath = PATH_KRENNAN_BACK;
|
||||
me->GetMotionMaster()->MoveSmoothPath(pathSize2, greymanesHorsePath2, pathSize2);
|
||||
break;
|
||||
case EVENT_DISMOUNT_PLAYER:
|
||||
if (Unit* passenger = me->GetVehicleKit()->GetPassenger(1))
|
||||
{
|
||||
if (Unit* player = me->GetVehicleKit()->GetPassenger(0))
|
||||
{
|
||||
if (Creature* krennan = passenger->ToCreature())
|
||||
{
|
||||
std::vector<Unit*> storedAttackers;
|
||||
|
||||
for (Unit* attacker : me->getAttackers())
|
||||
storedAttackers.push_back(attacker);
|
||||
|
||||
for (Unit* attacker : storedAttackers)
|
||||
{
|
||||
if (Creature* creature = attacker->ToCreature())
|
||||
if (creature->IsAIEnabled())
|
||||
creature->AI()->EnterEvadeMode();
|
||||
}
|
||||
|
||||
player->ExitVehicle();
|
||||
krennan->AddUnitState(UNIT_STATE_ROOT);
|
||||
krennan->ExitVehicle();
|
||||
krennan->AI()->Talk(SAY_RESCUED, me);
|
||||
krennan->DespawnOrUnsummon(7s);
|
||||
}
|
||||
}
|
||||
}
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
private:
|
||||
EventMap _events;
|
||||
uint32 _currentPath;
|
||||
};
|
||||
|
||||
/*######
|
||||
## Quest 14212 - Sacrifices
|
||||
######*/
|
||||
|
||||
enum CrowleysHorse
|
||||
{
|
||||
EVENT_JUMP_OVER_BARRICADES_1 = 1,
|
||||
EVENT_JUMP_OVER_BARRICADES_2,
|
||||
EVENT_MOVE_PATH_MAIN_1,
|
||||
EVENT_MOVE_PATH_MAIN_2,
|
||||
EVENT_MOVE_OFF_PATH = 6,
|
||||
|
||||
PATH_ID_CROWLEYS_HORSE_1 = 352310,
|
||||
PATH_ID_CROWLEYS_HORSE_2 = 352311,
|
||||
PATH_ID_CROWLEYS_HORSE_3 = 444280,
|
||||
|
||||
SPELL_THROW_TORCH = 67063,
|
||||
NPC_CROWLEYS_HORSE_2 = 44428,
|
||||
};
|
||||
|
||||
static Position const CrowleysHorseJumpPos = { -1714.762f, 1673.16f, 20.49182f };
|
||||
static Position const CrowleysHorseJumpPos2 = { -1566.71f, 1708.04f, 20.4849f };
|
||||
|
||||
struct npc_crowleys_horse : public VehicleAI
|
||||
{
|
||||
npc_crowleys_horse(Creature* creature) : VehicleAI(creature), _currentPath(0) { }
|
||||
|
||||
void PassengerBoarded(Unit* passenger, int8 /*seatId*/, bool apply) override
|
||||
{
|
||||
if (apply && passenger->GetTypeId() == TYPEID_PLAYER && me->GetEntry() != NPC_CROWLEYS_HORSE_2)
|
||||
{
|
||||
me->SetControlled(true, UNIT_STATE_ROOT);
|
||||
_events.ScheduleEvent(EVENT_JUMP_OVER_BARRICADES_1, 2s);
|
||||
}
|
||||
else if (apply && passenger->GetTypeId() == TYPEID_PLAYER)
|
||||
{
|
||||
me->SetControlled(true, UNIT_STATE_ROOT);
|
||||
_events.ScheduleEvent(EVENT_MOVE_OFF_PATH, 2s);
|
||||
}
|
||||
}
|
||||
|
||||
void MovementInform(uint32 type, uint32 pointId) override
|
||||
{
|
||||
if (type == WAYPOINT_MOTION_TYPE && pointId == 14 && _currentPath == PATH_ID_CROWLEYS_HORSE_1)
|
||||
_events.ScheduleEvent(EVENT_JUMP_OVER_BARRICADES_2, 1s);
|
||||
else if (type == WAYPOINT_MOTION_TYPE && pointId == 15 && _currentPath == PATH_ID_CROWLEYS_HORSE_2)
|
||||
_events.ScheduleEvent(EVENT_DISMOUNT_PLAYER, 1ms);
|
||||
else if (type == WAYPOINT_MOTION_TYPE && pointId == 16 && _currentPath == PATH_ID_CROWLEYS_HORSE_3)
|
||||
_events.ScheduleEvent(EVENT_DISMOUNT_PLAYER, 1ms);
|
||||
}
|
||||
|
||||
void UpdateAI(uint32 diff) override
|
||||
{
|
||||
_events.Update(diff);
|
||||
|
||||
while (uint32 eventId = _events.ExecuteEvent())
|
||||
{
|
||||
switch (eventId)
|
||||
{
|
||||
case EVENT_JUMP_OVER_BARRICADES_1:
|
||||
me->SetControlled(false, UNIT_STATE_ROOT);
|
||||
me->GetMotionMaster()->MoveJump(CrowleysHorseJumpPos, 16.0f, 18.56182f);
|
||||
_events.ScheduleEvent(EVENT_MOVE_PATH_MAIN_1, 2s);
|
||||
break;
|
||||
case EVENT_MOVE_PATH_MAIN_1:
|
||||
me->GetMotionMaster()->MovePoint(1001, -1686.12f, 1655.64f, 20.6106f);
|
||||
_currentPath = PATH_ID_CROWLEYS_HORSE_1;
|
||||
break;
|
||||
case EVENT_JUMP_OVER_BARRICADES_2:
|
||||
me->GetMotionMaster()->MoveJump(CrowleysHorseJumpPos2, 16.0f, 18.56182f);
|
||||
_events.ScheduleEvent(EVENT_MOVE_PATH_MAIN_2, 2s);
|
||||
break;
|
||||
case EVENT_MOVE_PATH_MAIN_2:
|
||||
_currentPath = PATH_ID_CROWLEYS_HORSE_2;
|
||||
me->GetMotionMaster()->MovePath(PATH_ID_CROWLEYS_HORSE_2, false);
|
||||
break;
|
||||
case EVENT_DISMOUNT_PLAYER:
|
||||
{
|
||||
std::set<Unit*> attackersCopy = me->getAttackers();
|
||||
for (Unit* attacker : attackersCopy)
|
||||
{
|
||||
if (Creature* creature = attacker->ToCreature())
|
||||
if (creature->IsAIEnabled())
|
||||
creature->AI()->EnterEvadeMode();
|
||||
}
|
||||
|
||||
me->RemoveAurasDueToSpell(VEHICLE_SPELL_RIDE_HARDCODED);
|
||||
me->DespawnOrUnsummon(5s);
|
||||
break;
|
||||
}
|
||||
case EVENT_MOVE_OFF_PATH:
|
||||
me->SetControlled(false, UNIT_STATE_ROOT);
|
||||
_currentPath = PATH_ID_CROWLEYS_HORSE_3;
|
||||
me->GetMotionMaster()->MovePath(PATH_ID_CROWLEYS_HORSE_3, false);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
private:
|
||||
EventMap _events;
|
||||
uint32 _currentPath;
|
||||
};
|
||||
|
||||
enum GileanCrow
|
||||
{
|
||||
SPELL_GILNEAN_CROW = 93275,
|
||||
EVENT_APPLY_HOVER_BYTES = 1,
|
||||
EVENT_FLY_AWAY_1 = 2,
|
||||
EVENT_FLY_AWAY_2 = 3,
|
||||
|
||||
POINT_NONE = 0,
|
||||
POINT_CROW_FLIGHT = 1
|
||||
};
|
||||
|
||||
struct npc_gilnean_crow : public PassiveAI
|
||||
{
|
||||
npc_gilnean_crow(Creature* creature) : PassiveAI(creature) { }
|
||||
|
||||
void SpellHit(WorldObject* /*caster*/, SpellInfo const* spell) override
|
||||
{
|
||||
if (spell->Id == SPELL_GILNEAN_CROW)
|
||||
_events.ScheduleEvent(EVENT_APPLY_HOVER_BYTES, 500ms);
|
||||
}
|
||||
|
||||
void MovementInform(uint32 type, uint32 pointId) override
|
||||
{
|
||||
if (type == POINT_MOTION_TYPE && pointId == POINT_CROW_FLIGHT)
|
||||
{
|
||||
Position pos = me->GetRandomNearPosition(8.0f);
|
||||
pos.m_positionZ = me->GetPositionZ() + 10.0f;
|
||||
me->GetMotionMaster()->MovePoint(POINT_NONE, pos);
|
||||
me->DespawnOrUnsummon(14s);
|
||||
}
|
||||
}
|
||||
|
||||
void UpdateAI(uint32 diff) override
|
||||
{
|
||||
_events.Update(diff);
|
||||
|
||||
while (uint32 eventId = _events.ExecuteEvent())
|
||||
{
|
||||
switch (eventId)
|
||||
{
|
||||
case EVENT_APPLY_HOVER_BYTES:
|
||||
me->SetStandState(UNIT_STAND_STATE_SIT);
|
||||
me->SetAnimTier(AnimTier::Submerged);
|
||||
_events.ScheduleEvent(EVENT_FLY_AWAY_1, 1s);
|
||||
break;
|
||||
case EVENT_FLY_AWAY_1:
|
||||
{
|
||||
Position pos = me->GetRandomNearPosition(7.0f);
|
||||
pos.m_positionZ = me->GetPositionZ() + 8.0f;
|
||||
me->GetMotionMaster()->MovePoint(POINT_CROW_FLIGHT, pos);
|
||||
break;
|
||||
}
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
private:
|
||||
EventMap _events;
|
||||
};
|
||||
}
|
||||
|
||||
void AddSC_gilneas_chapter_1()
|
||||
{
|
||||
using namespace Gilneas::Chapter1;
|
||||
RegisterCreatureAI(npc_frightened_citizen);
|
||||
RegisterCreatureAI(npc_worgen_runt);
|
||||
RegisterCreatureAI(npc_josiah_avery);
|
||||
RegisterCreatureAI(npc_greymanes_horse);
|
||||
RegisterCreatureAI(npc_crowleys_horse);
|
||||
RegisterCreatureAI(npc_gilnean_crow);
|
||||
}
|
||||
@@ -0,0 +1,671 @@
|
||||
/*
|
||||
* Vcore - Arathi Highlands
|
||||
* Quest 26050: Goggle Boggle
|
||||
*/
|
||||
|
||||
#include "ScriptMgr.h"
|
||||
#include "ScriptedCreature.h"
|
||||
#include "Player.h"
|
||||
#include "ObjectAccessor.h"
|
||||
#include "MotionMaster.h"
|
||||
#include "TemporarySummon.h"
|
||||
|
||||
enum PhizzlethorpeData
|
||||
{
|
||||
QUEST_GOGGLE_BOGGLE = 26050,
|
||||
|
||||
NPC_VENGEFUL_SURGE = 2776,
|
||||
|
||||
POINT_OUTBOUND_1 = 1,
|
||||
POINT_CAVE_OUTSIDE = 2,
|
||||
POINT_CAVE_INSIDE = 3,
|
||||
POINT_STONE = 4,
|
||||
POINT_RETURN_CAVE_IN = 5,
|
||||
POINT_RETURN_CAVE_OUT = 6,
|
||||
POINT_RETURN_1 = 7,
|
||||
POINT_RETURN_HOME = 8,
|
||||
|
||||
POINT_CAVE_LEFT_1 = 9,
|
||||
POINT_CAVE_LEFT_2 = 10,
|
||||
|
||||
EVENT_START_MOVING = 1,
|
||||
EVENT_ENTER_CAVE = 2,
|
||||
EVENT_START_WORKING = 3,
|
||||
EVENT_SPAWN_AMBUSH = 4,
|
||||
EVENT_ALMOST_DONE = 5,
|
||||
EVENT_BEGIN_RETURN = 6,
|
||||
EVENT_FINISH_QUEST = 7,
|
||||
EVENT_CHECK_PLAYER = 8,
|
||||
EVENT_TIMEOUT = 9
|
||||
};
|
||||
|
||||
static Position const PhizzlethorpeStart =
|
||||
{
|
||||
-2066.46f, -2085.97f, 8.92410f, 0.0f
|
||||
};
|
||||
|
||||
static Position const PhizzlethorpePoint1 =
|
||||
{
|
||||
-2078.255371f, -2092.058105f, 9.744357f, 0.0f
|
||||
};
|
||||
|
||||
static Position const PhizzlethorpeCaveOutside =
|
||||
{
|
||||
-2072.194580f, -2127.455322f, 19.688131f, 0.0f
|
||||
};
|
||||
|
||||
/*
|
||||
* Point recorded directly behind the cave threshold.
|
||||
* This prevents MovePoint from trying to cut sideways through
|
||||
* the cave wall when travelling from outside to the rune stone.
|
||||
*/
|
||||
static Position const PhizzlethorpeCaveInside =
|
||||
{
|
||||
-2069.170166f, -2129.804443f, 21.074938f, 5.562022f
|
||||
};
|
||||
|
||||
static Position const PhizzlethorpeCaveLeft1 =
|
||||
{
|
||||
-2062.205322f, -2129.687500f, 21.316385f, 5.452067f
|
||||
};
|
||||
|
||||
static Position const PhizzlethorpeCaveLeft2 =
|
||||
{
|
||||
-2056.376465f, -2137.908203f, 20.625616f, 5.419080f
|
||||
};
|
||||
|
||||
static Position const PhizzlethorpeStone =
|
||||
{
|
||||
-2043.507202f, -2153.291016f, 20.177914f, 0.0f
|
||||
};
|
||||
|
||||
static Position const SurgePosition1 =
|
||||
{
|
||||
-2040.5f, -2150.5f, 20.17f, 0.0f
|
||||
};
|
||||
|
||||
static Position const SurgePosition2 =
|
||||
{
|
||||
-2046.5f, -2155.5f, 20.17f, 0.0f
|
||||
};
|
||||
|
||||
class npc_professor_phizzlethorpe : public CreatureScript
|
||||
{
|
||||
public:
|
||||
npc_professor_phizzlethorpe() : CreatureScript("npc_professor_phizzlethorpe") { }
|
||||
|
||||
struct npc_professor_phizzlethorpeAI : public ScriptedAI
|
||||
{
|
||||
npc_professor_phizzlethorpeAI(Creature* creature)
|
||||
: ScriptedAI(creature), summons(me)
|
||||
{
|
||||
}
|
||||
|
||||
EventMap events;
|
||||
SummonList summons;
|
||||
ObjectGuid playerGuid;
|
||||
|
||||
uint8 surgeDeaths = 0;
|
||||
uint8 surgeCount = 0;
|
||||
|
||||
bool eventActive = false;
|
||||
bool returning = false;
|
||||
bool waitingForAmbush = false;
|
||||
|
||||
void ResetWorkEmote()
|
||||
{
|
||||
me->SetUInt32Value(UNIT_NPC_EMOTESTATE, 0);
|
||||
}
|
||||
|
||||
void Reset() override
|
||||
{
|
||||
/*
|
||||
* Combat evade/reset can happen during an active escort.
|
||||
* Do not destroy the running quest state in that case.
|
||||
*/
|
||||
if (eventActive)
|
||||
return;
|
||||
|
||||
events.Reset();
|
||||
summons.DespawnAll();
|
||||
|
||||
playerGuid.Clear();
|
||||
|
||||
surgeDeaths = 0;
|
||||
surgeCount = 0;
|
||||
|
||||
returning = false;
|
||||
waitingForAmbush = false;
|
||||
|
||||
ResetWorkEmote();
|
||||
}
|
||||
|
||||
Player* GetEscortPlayer()
|
||||
{
|
||||
if (playerGuid.IsEmpty())
|
||||
return nullptr;
|
||||
|
||||
return ObjectAccessor::GetPlayer(*me, playerGuid);
|
||||
}
|
||||
|
||||
void StartEvent(Player* player)
|
||||
{
|
||||
if (!player || eventActive)
|
||||
return;
|
||||
|
||||
eventActive = true;
|
||||
returning = false;
|
||||
waitingForAmbush = false;
|
||||
|
||||
surgeDeaths = 0;
|
||||
surgeCount = 0;
|
||||
|
||||
playerGuid = player->GetGUID();
|
||||
|
||||
events.Reset();
|
||||
summons.DespawnAll();
|
||||
|
||||
me->CombatStop(true);
|
||||
ResetWorkEmote();
|
||||
|
||||
/*
|
||||
* Outbound trip is deliberately walked.
|
||||
*/
|
||||
me->SetWalk(true);
|
||||
|
||||
/*
|
||||
* creature_text group 0:
|
||||
* "Ok, $n. Follow me to the cave..."
|
||||
*/
|
||||
Talk(0, player);
|
||||
|
||||
/*
|
||||
* Give the opening line time to breathe before moving.
|
||||
*/
|
||||
events.ScheduleEvent(EVENT_START_MOVING, 3000);
|
||||
|
||||
events.ScheduleEvent(EVENT_CHECK_PLAYER, 1000);
|
||||
events.ScheduleEvent(EVENT_TIMEOUT, 300000);
|
||||
}
|
||||
|
||||
void AbortEvent(bool failQuest)
|
||||
{
|
||||
Player* player = GetEscortPlayer();
|
||||
|
||||
if (failQuest && player &&
|
||||
player->GetQuestStatus(QUEST_GOGGLE_BOGGLE) == QUEST_STATUS_INCOMPLETE)
|
||||
{
|
||||
player->FailQuest(QUEST_GOGGLE_BOGGLE);
|
||||
}
|
||||
|
||||
events.Reset();
|
||||
summons.DespawnAll();
|
||||
|
||||
ResetWorkEmote();
|
||||
|
||||
eventActive = false;
|
||||
returning = false;
|
||||
waitingForAmbush = false;
|
||||
|
||||
surgeDeaths = 0;
|
||||
surgeCount = 0;
|
||||
|
||||
playerGuid.Clear();
|
||||
|
||||
me->CombatStop(true);
|
||||
me->GetMotionMaster()->Clear();
|
||||
|
||||
/*
|
||||
* Do NOT despawn the professor.
|
||||
* He is the real questgiver spawn and has a long DB respawn.
|
||||
*/
|
||||
me->SetWalk(false);
|
||||
me->GetMotionMaster()->MoveTargetedHome();
|
||||
}
|
||||
|
||||
void AmbushFinished()
|
||||
{
|
||||
if (!eventActive || !waitingForAmbush)
|
||||
return;
|
||||
|
||||
waitingForAmbush = false;
|
||||
|
||||
/*
|
||||
* Stop the "working on the goggles" animation now that
|
||||
* the extraction is finished.
|
||||
*/
|
||||
ResetWorkEmote();
|
||||
|
||||
me->CombatStop(true);
|
||||
|
||||
/*
|
||||
* Small pause after the last attacker dies.
|
||||
*/
|
||||
events.ScheduleEvent(EVENT_BEGIN_RETURN, 2000);
|
||||
}
|
||||
|
||||
void FinishEvent()
|
||||
{
|
||||
Player* player = GetEscortPlayer();
|
||||
|
||||
if (player &&
|
||||
player->GetQuestStatus(QUEST_GOGGLE_BOGGLE) == QUEST_STATUS_INCOMPLETE)
|
||||
{
|
||||
/*
|
||||
* creature_text group 9:
|
||||
* Doctor Draxlegauge will give you further instructions...
|
||||
*/
|
||||
Talk(9, player);
|
||||
|
||||
player->GroupEventHappens(QUEST_GOGGLE_BOGGLE, me);
|
||||
}
|
||||
|
||||
events.Reset();
|
||||
summons.DespawnAll();
|
||||
|
||||
ResetWorkEmote();
|
||||
|
||||
eventActive = false;
|
||||
returning = false;
|
||||
waitingForAmbush = false;
|
||||
|
||||
surgeDeaths = 0;
|
||||
surgeCount = 0;
|
||||
|
||||
playerGuid.Clear();
|
||||
|
||||
me->CombatStop(true);
|
||||
me->SetWalk(false);
|
||||
}
|
||||
|
||||
void QuestAccept(Player* player, Quest const* quest) override
|
||||
{
|
||||
if (quest->GetQuestId() == QUEST_GOGGLE_BOGGLE)
|
||||
StartEvent(player);
|
||||
}
|
||||
|
||||
void MovementInform(uint32 type, uint32 id) override
|
||||
{
|
||||
if (type != POINT_MOTION_TYPE || !eventActive)
|
||||
return;
|
||||
|
||||
switch (id)
|
||||
{
|
||||
case POINT_OUTBOUND_1:
|
||||
me->GetMotionMaster()->MovePoint(
|
||||
POINT_CAVE_OUTSIDE,
|
||||
PhizzlethorpeCaveOutside);
|
||||
break;
|
||||
|
||||
case POINT_CAVE_OUTSIDE:
|
||||
{
|
||||
/*
|
||||
* creature_text group 1:
|
||||
* "I discovered this cave on our first day here..."
|
||||
*/
|
||||
if (Player* player = GetEscortPlayer())
|
||||
Talk(1, player);
|
||||
|
||||
/*
|
||||
* Five-second pause outside before entering.
|
||||
*/
|
||||
events.ScheduleEvent(EVENT_ENTER_CAVE, 5000);
|
||||
break;
|
||||
}
|
||||
|
||||
case POINT_CAVE_INSIDE:
|
||||
me->GetMotionMaster()->MovePoint(
|
||||
POINT_CAVE_LEFT_1,
|
||||
PhizzlethorpeCaveLeft1);
|
||||
break;
|
||||
|
||||
case POINT_CAVE_LEFT_1:
|
||||
me->GetMotionMaster()->MovePoint(
|
||||
POINT_CAVE_LEFT_2,
|
||||
PhizzlethorpeCaveLeft2);
|
||||
break;
|
||||
|
||||
case POINT_CAVE_LEFT_2:
|
||||
me->GetMotionMaster()->MovePoint(
|
||||
POINT_STONE,
|
||||
PhizzlethorpeStone);
|
||||
break;
|
||||
|
||||
case POINT_STONE:
|
||||
{
|
||||
/*
|
||||
* creature_text group 2:
|
||||
* Player is told to defend the professor.
|
||||
*/
|
||||
if (Player* player = GetEscortPlayer())
|
||||
Talk(2, player);
|
||||
|
||||
/*
|
||||
* He studies/prepares at the stone for five seconds.
|
||||
*/
|
||||
events.ScheduleEvent(EVENT_START_WORKING, 5000);
|
||||
break;
|
||||
}
|
||||
|
||||
case POINT_RETURN_CAVE_IN:
|
||||
me->GetMotionMaster()->MovePoint(
|
||||
POINT_RETURN_CAVE_OUT,
|
||||
PhizzlethorpeCaveOutside);
|
||||
break;
|
||||
|
||||
case POINT_RETURN_CAVE_OUT:
|
||||
{
|
||||
/*
|
||||
* creature_text group 7:
|
||||
* "Phew! Glad to be back from that creepy cave."
|
||||
*/
|
||||
if (Player* player = GetEscortPlayer())
|
||||
Talk(7, player);
|
||||
|
||||
me->GetMotionMaster()->MovePoint(
|
||||
POINT_RETURN_1,
|
||||
PhizzlethorpePoint1);
|
||||
break;
|
||||
}
|
||||
|
||||
case POINT_RETURN_1:
|
||||
me->GetMotionMaster()->MovePoint(
|
||||
POINT_RETURN_HOME,
|
||||
PhizzlethorpeStart);
|
||||
break;
|
||||
|
||||
case POINT_RETURN_HOME:
|
||||
{
|
||||
/*
|
||||
* creature_text group 8:
|
||||
* emote about handing the glowing goggles over.
|
||||
*/
|
||||
if (Player* player = GetEscortPlayer())
|
||||
Talk(8, player);
|
||||
|
||||
/*
|
||||
* Give the final emote a moment before the last line
|
||||
* and quest completion.
|
||||
*/
|
||||
events.ScheduleEvent(EVENT_FINISH_QUEST, 2000);
|
||||
break;
|
||||
}
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void JustSummoned(Creature* summon) override
|
||||
{
|
||||
summons.Summon(summon);
|
||||
|
||||
if (summon->GetEntry() == NPC_VENGEFUL_SURGE)
|
||||
{
|
||||
++surgeCount;
|
||||
|
||||
/*
|
||||
* The player is supposed to protect Phizzlethorpe.
|
||||
*/
|
||||
summon->AI()->AttackStart(me);
|
||||
}
|
||||
}
|
||||
|
||||
void SummonedCreatureDies(Creature* summon, Unit* /*killer*/) override
|
||||
{
|
||||
if (!eventActive ||
|
||||
!waitingForAmbush ||
|
||||
summon->GetEntry() != NPC_VENGEFUL_SURGE)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
++surgeDeaths;
|
||||
|
||||
if (surgeCount > 0 && surgeDeaths >= surgeCount)
|
||||
AmbushFinished();
|
||||
}
|
||||
|
||||
void JustDied(Unit* /*killer*/) override
|
||||
{
|
||||
ResetWorkEmote();
|
||||
|
||||
if (Player* player = GetEscortPlayer())
|
||||
{
|
||||
if (player->GetQuestStatus(QUEST_GOGGLE_BOGGLE) == QUEST_STATUS_INCOMPLETE)
|
||||
player->FailQuest(QUEST_GOGGLE_BOGGLE);
|
||||
}
|
||||
|
||||
events.Reset();
|
||||
summons.DespawnAll();
|
||||
|
||||
eventActive = false;
|
||||
returning = false;
|
||||
waitingForAmbush = false;
|
||||
}
|
||||
|
||||
void UpdateAI(uint32 diff) override
|
||||
{
|
||||
events.Update(diff);
|
||||
|
||||
while (uint32 eventId = events.ExecuteEvent())
|
||||
{
|
||||
switch (eventId)
|
||||
{
|
||||
case EVENT_START_MOVING:
|
||||
{
|
||||
if (!eventActive)
|
||||
break;
|
||||
|
||||
me->GetMotionMaster()->MovePoint(
|
||||
POINT_OUTBOUND_1,
|
||||
PhizzlethorpePoint1);
|
||||
break;
|
||||
}
|
||||
|
||||
case EVENT_ENTER_CAVE:
|
||||
{
|
||||
if (!eventActive || returning)
|
||||
break;
|
||||
|
||||
/*
|
||||
* Newly recorded point immediately behind the cave
|
||||
* threshold. This forces a sane crossing of the entrance.
|
||||
*/
|
||||
me->GetMotionMaster()->MovePoint(
|
||||
POINT_CAVE_INSIDE,
|
||||
PhizzlethorpeCaveInside);
|
||||
break;
|
||||
}
|
||||
|
||||
case EVENT_START_WORKING:
|
||||
{
|
||||
if (!eventActive || returning)
|
||||
break;
|
||||
|
||||
/*
|
||||
* creature_text group 3 is the visible emote text:
|
||||
* "...begins tinkering with the goggles..."
|
||||
*/
|
||||
if (Player* player = GetEscortPlayer())
|
||||
Talk(3, player);
|
||||
|
||||
/*
|
||||
* Emote 69 / STATE_USESTANDING.
|
||||
* Verified ingame for this character/model.
|
||||
*/
|
||||
me->SetUInt32Value(
|
||||
UNIT_NPC_EMOTESTATE,
|
||||
EMOTE_STATE_USE_STANDING);
|
||||
|
||||
/*
|
||||
* Let him visibly work for four seconds before
|
||||
* anything attacks.
|
||||
*/
|
||||
events.ScheduleEvent(EVENT_SPAWN_AMBUSH, 4000);
|
||||
break;
|
||||
}
|
||||
|
||||
case EVENT_SPAWN_AMBUSH:
|
||||
{
|
||||
if (!eventActive || returning)
|
||||
break;
|
||||
|
||||
waitingForAmbush = true;
|
||||
surgeDeaths = 0;
|
||||
surgeCount = 0;
|
||||
|
||||
/*
|
||||
* creature_text group 4 has two possible help lines.
|
||||
*/
|
||||
if (Player* player = GetEscortPlayer())
|
||||
Talk(4, player);
|
||||
|
||||
me->SummonCreature(
|
||||
NPC_VENGEFUL_SURGE,
|
||||
SurgePosition1,
|
||||
TEMPSUMMON_CORPSE_TIMED_DESPAWN,
|
||||
5000);
|
||||
|
||||
me->SummonCreature(
|
||||
NPC_VENGEFUL_SURGE,
|
||||
SurgePosition2,
|
||||
TEMPSUMMON_CORPSE_TIMED_DESPAWN,
|
||||
5000);
|
||||
|
||||
/*
|
||||
* Mid-event encouragement.
|
||||
*/
|
||||
events.ScheduleEvent(EVENT_ALMOST_DONE, 5000);
|
||||
|
||||
/*
|
||||
* If neither creature could be created, do not leave
|
||||
* the sole questgiver trapped forever.
|
||||
*/
|
||||
if (surgeCount == 0)
|
||||
AbortEvent(true);
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
case EVENT_ALMOST_DONE:
|
||||
{
|
||||
if (eventActive && waitingForAmbush)
|
||||
{
|
||||
/*
|
||||
* creature_text group 5:
|
||||
* "Almost done! Just a little longer!"
|
||||
*/
|
||||
if (Player* player = GetEscortPlayer())
|
||||
Talk(5, player);
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
case EVENT_BEGIN_RETURN:
|
||||
{
|
||||
if (!eventActive)
|
||||
break;
|
||||
|
||||
returning = true;
|
||||
waitingForAmbush = false;
|
||||
|
||||
ResetWorkEmote();
|
||||
|
||||
/*
|
||||
* creature_text group 6:
|
||||
* "I've done it!... Let's get out of here!"
|
||||
*/
|
||||
if (Player* player = GetEscortPlayer())
|
||||
Talk(6, player);
|
||||
|
||||
/*
|
||||
* Return trip may be faster than the deliberate
|
||||
* outbound walk.
|
||||
*/
|
||||
me->SetWalk(false);
|
||||
|
||||
me->GetMotionMaster()->MovePoint(
|
||||
POINT_RETURN_CAVE_IN,
|
||||
PhizzlethorpeCaveInside);
|
||||
break;
|
||||
}
|
||||
|
||||
case EVENT_FINISH_QUEST:
|
||||
{
|
||||
if (eventActive)
|
||||
FinishEvent();
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
case EVENT_CHECK_PLAYER:
|
||||
{
|
||||
if (!eventActive)
|
||||
break;
|
||||
|
||||
Player* player = GetEscortPlayer();
|
||||
|
||||
/*
|
||||
* Logout / object no longer available:
|
||||
* restore professor without artificially failing
|
||||
* an offline player's quest.
|
||||
*/
|
||||
if (!player)
|
||||
{
|
||||
AbortEvent(false);
|
||||
break;
|
||||
}
|
||||
|
||||
/*
|
||||
* Player abandoned or otherwise no longer has the
|
||||
* quest active.
|
||||
*/
|
||||
if (player->GetQuestStatus(QUEST_GOGGLE_BOGGLE) != QUEST_STATUS_INCOMPLETE)
|
||||
{
|
||||
AbortEvent(false);
|
||||
break;
|
||||
}
|
||||
|
||||
/*
|
||||
* Actual escort failure.
|
||||
*/
|
||||
if (!player->IsAlive() || me->GetDistance(player) > 120.0f)
|
||||
{
|
||||
AbortEvent(true);
|
||||
break;
|
||||
}
|
||||
|
||||
events.ScheduleEvent(EVENT_CHECK_PLAYER, 1000);
|
||||
break;
|
||||
}
|
||||
|
||||
case EVENT_TIMEOUT:
|
||||
{
|
||||
if (eventActive)
|
||||
AbortEvent(true);
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (UpdateVictim())
|
||||
DoMeleeAttackIfReady();
|
||||
}
|
||||
};
|
||||
|
||||
CreatureAI* GetAI(Creature* creature) const override
|
||||
{
|
||||
return new npc_professor_phizzlethorpeAI(creature);
|
||||
}
|
||||
};
|
||||
|
||||
void AddSC_arathi_highlands()
|
||||
{
|
||||
new npc_professor_phizzlethorpe();
|
||||
}
|
||||
@@ -0,0 +1,409 @@
|
||||
#include "ScriptMgr.h"
|
||||
#include "ScriptedCreature.h"
|
||||
#include "Player.h"
|
||||
#include "ObjectAccessor.h"
|
||||
#include "MotionMaster.h"
|
||||
#include "TemporarySummon.h"
|
||||
|
||||
enum GoggleBoggle
|
||||
{
|
||||
QUEST_GOGGLE_BOGGLE = 26050,
|
||||
NPC_VENGEFUL_SURGE = 2776,
|
||||
|
||||
POINT_P1 = 1,
|
||||
POINT_P2 = 2,
|
||||
POINT_P3 = 3,
|
||||
POINT_RETURN_P2 = 4,
|
||||
POINT_RETURN_P1 = 5,
|
||||
POINT_RETURN_HOME = 6,
|
||||
|
||||
EVENT_CHECK_PLAYER = 1,
|
||||
EVENT_TIMEOUT = 2
|
||||
};
|
||||
|
||||
Position const PhizzlethorpeP1 =
|
||||
{
|
||||
-2078.255371f, -2092.058105f, 9.744357f, 0.0f
|
||||
};
|
||||
|
||||
Position const PhizzlethorpeP2 =
|
||||
{
|
||||
-2072.194580f, -2127.455322f, 19.688131f, 0.0f
|
||||
};
|
||||
|
||||
Position const PhizzlethorpeP3 =
|
||||
{
|
||||
-2043.507202f, -2153.291016f, 20.177914f, 0.0f
|
||||
};
|
||||
|
||||
Position const PhizzlethorpeHome =
|
||||
{
|
||||
-2066.46f, -2085.97f, 8.92410f, 0.0f
|
||||
};
|
||||
|
||||
class npc_professor_phizzlethorpe : public CreatureScript
|
||||
{
|
||||
public:
|
||||
npc_professor_phizzlethorpe()
|
||||
: CreatureScript("npc_professor_phizzlethorpe")
|
||||
{
|
||||
}
|
||||
|
||||
struct npc_professor_phizzlethorpeAI : public ScriptedAI
|
||||
{
|
||||
npc_professor_phizzlethorpeAI(Creature* creature)
|
||||
: ScriptedAI(creature), summons(me)
|
||||
{
|
||||
}
|
||||
|
||||
EventMap events;
|
||||
SummonList summons;
|
||||
|
||||
ObjectGuid playerGuid;
|
||||
|
||||
uint8 surgeDeaths = 0;
|
||||
uint8 surgeCount = 0;
|
||||
|
||||
bool eventActive = false;
|
||||
bool returning = false;
|
||||
bool waitingForAmbush = false;
|
||||
|
||||
void Reset() override
|
||||
{
|
||||
// Do not destroy an active escort merely because combat AI resets.
|
||||
if (eventActive)
|
||||
return;
|
||||
|
||||
events.Reset();
|
||||
summons.DespawnAll();
|
||||
|
||||
playerGuid.Clear();
|
||||
|
||||
surgeDeaths = 0;
|
||||
surgeCount = 0;
|
||||
|
||||
returning = false;
|
||||
waitingForAmbush = false;
|
||||
}
|
||||
|
||||
void StartEvent(Player* player)
|
||||
{
|
||||
if (!player || eventActive)
|
||||
return;
|
||||
|
||||
playerGuid = player->GetGUID();
|
||||
|
||||
surgeDeaths = 0;
|
||||
surgeCount = 0;
|
||||
|
||||
eventActive = true;
|
||||
returning = false;
|
||||
waitingForAmbush = false;
|
||||
|
||||
events.Reset();
|
||||
summons.DespawnAll();
|
||||
|
||||
// Check regularly whether the player can still continue the event.
|
||||
events.ScheduleEvent(EVENT_CHECK_PLAYER, 1000);
|
||||
|
||||
// Hard safety timeout: the professor must never remain stuck forever.
|
||||
events.ScheduleEvent(EVENT_TIMEOUT, 300000);
|
||||
|
||||
me->GetMotionMaster()->MovePoint(
|
||||
POINT_P1,
|
||||
PhizzlethorpeP1.GetPositionX(),
|
||||
PhizzlethorpeP1.GetPositionY(),
|
||||
PhizzlethorpeP1.GetPositionZ()
|
||||
);
|
||||
}
|
||||
|
||||
void AbortEvent(bool failQuest)
|
||||
{
|
||||
if (!eventActive)
|
||||
return;
|
||||
|
||||
if (failQuest)
|
||||
{
|
||||
if (Player* player = ObjectAccessor::GetPlayer(*me, playerGuid))
|
||||
{
|
||||
if (player->GetQuestStatus(QUEST_GOGGLE_BOGGLE) == QUEST_STATUS_INCOMPLETE)
|
||||
player->FailQuest(QUEST_GOGGLE_BOGGLE);
|
||||
}
|
||||
}
|
||||
|
||||
events.Reset();
|
||||
summons.DespawnAll();
|
||||
|
||||
eventActive = false;
|
||||
returning = false;
|
||||
waitingForAmbush = false;
|
||||
|
||||
surgeDeaths = 0;
|
||||
surgeCount = 0;
|
||||
|
||||
playerGuid.Clear();
|
||||
|
||||
me->CombatStop(true);
|
||||
me->GetMotionMaster()->Clear();
|
||||
me->GetMotionMaster()->MoveTargetedHome();
|
||||
}
|
||||
|
||||
void BeginReturn()
|
||||
{
|
||||
if (!eventActive || returning)
|
||||
return;
|
||||
|
||||
waitingForAmbush = false;
|
||||
returning = true;
|
||||
|
||||
me->CombatStop(true);
|
||||
|
||||
me->GetMotionMaster()->MovePoint(
|
||||
POINT_RETURN_P2,
|
||||
PhizzlethorpeP2.GetPositionX(),
|
||||
PhizzlethorpeP2.GetPositionY(),
|
||||
PhizzlethorpeP2.GetPositionZ()
|
||||
);
|
||||
}
|
||||
|
||||
void FinishEvent()
|
||||
{
|
||||
if (!eventActive)
|
||||
return;
|
||||
|
||||
if (Player* player = ObjectAccessor::GetPlayer(*me, playerGuid))
|
||||
{
|
||||
if (player->GetQuestStatus(QUEST_GOGGLE_BOGGLE) == QUEST_STATUS_INCOMPLETE)
|
||||
player->GroupEventHappens(QUEST_GOGGLE_BOGGLE, me);
|
||||
}
|
||||
|
||||
events.Reset();
|
||||
summons.DespawnAll();
|
||||
|
||||
eventActive = false;
|
||||
returning = false;
|
||||
waitingForAmbush = false;
|
||||
|
||||
surgeDeaths = 0;
|
||||
surgeCount = 0;
|
||||
|
||||
playerGuid.Clear();
|
||||
}
|
||||
|
||||
void QuestAccept(Player* player, Quest const* quest) override
|
||||
{
|
||||
if (quest->GetQuestId() == QUEST_GOGGLE_BOGGLE)
|
||||
StartEvent(player);
|
||||
}
|
||||
|
||||
void MovementInform(uint32 type, uint32 id) override
|
||||
{
|
||||
if (type != POINT_MOTION_TYPE || !eventActive)
|
||||
return;
|
||||
|
||||
switch (id)
|
||||
{
|
||||
case POINT_P1:
|
||||
me->GetMotionMaster()->MovePoint(
|
||||
POINT_P2,
|
||||
PhizzlethorpeP2.GetPositionX(),
|
||||
PhizzlethorpeP2.GetPositionY(),
|
||||
PhizzlethorpeP2.GetPositionZ()
|
||||
);
|
||||
break;
|
||||
|
||||
case POINT_P2:
|
||||
me->GetMotionMaster()->MovePoint(
|
||||
POINT_P3,
|
||||
PhizzlethorpeP3.GetPositionX(),
|
||||
PhizzlethorpeP3.GetPositionY(),
|
||||
PhizzlethorpeP3.GetPositionZ()
|
||||
);
|
||||
break;
|
||||
|
||||
case POINT_P3:
|
||||
{
|
||||
waitingForAmbush = true;
|
||||
surgeDeaths = 0;
|
||||
surgeCount = 0;
|
||||
|
||||
Position surgePos1 =
|
||||
{
|
||||
-2040.5f, -2150.5f, 20.17f, 0.0f
|
||||
};
|
||||
|
||||
Position surgePos2 =
|
||||
{
|
||||
-2046.5f, -2155.5f, 20.17f, 0.0f
|
||||
};
|
||||
|
||||
if (TempSummon* surge = me->SummonCreature(
|
||||
NPC_VENGEFUL_SURGE,
|
||||
surgePos1,
|
||||
TEMPSUMMON_CORPSE_TIMED_DESPAWN,
|
||||
5000))
|
||||
{
|
||||
surge->AI()->AttackStart(me);
|
||||
}
|
||||
|
||||
if (TempSummon* surge = me->SummonCreature(
|
||||
NPC_VENGEFUL_SURGE,
|
||||
surgePos2,
|
||||
TEMPSUMMON_CORPSE_TIMED_DESPAWN,
|
||||
5000))
|
||||
{
|
||||
surge->AI()->AttackStart(me);
|
||||
}
|
||||
|
||||
// If neither summon could be created, do not leave the
|
||||
// professor permanently stuck in the cave.
|
||||
if (surgeCount == 0)
|
||||
AbortEvent(true);
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
case POINT_RETURN_P2:
|
||||
me->GetMotionMaster()->MovePoint(
|
||||
POINT_RETURN_P1,
|
||||
PhizzlethorpeP1.GetPositionX(),
|
||||
PhizzlethorpeP1.GetPositionY(),
|
||||
PhizzlethorpeP1.GetPositionZ()
|
||||
);
|
||||
break;
|
||||
|
||||
case POINT_RETURN_P1:
|
||||
me->GetMotionMaster()->MovePoint(
|
||||
POINT_RETURN_HOME,
|
||||
PhizzlethorpeHome.GetPositionX(),
|
||||
PhizzlethorpeHome.GetPositionY(),
|
||||
PhizzlethorpeHome.GetPositionZ()
|
||||
);
|
||||
break;
|
||||
|
||||
case POINT_RETURN_HOME:
|
||||
FinishEvent();
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void JustSummoned(Creature* summon) override
|
||||
{
|
||||
summons.Summon(summon);
|
||||
|
||||
if (summon->GetEntry() == NPC_VENGEFUL_SURGE)
|
||||
++surgeCount;
|
||||
}
|
||||
|
||||
void SummonedCreatureDies(Creature* summon, Unit* /*killer*/) override
|
||||
{
|
||||
if (!eventActive || !waitingForAmbush)
|
||||
return;
|
||||
|
||||
if (!summon || summon->GetEntry() != NPC_VENGEFUL_SURGE)
|
||||
return;
|
||||
|
||||
++surgeDeaths;
|
||||
|
||||
if (surgeCount > 0 && surgeDeaths >= surgeCount)
|
||||
BeginReturn();
|
||||
}
|
||||
|
||||
void JustDied(Unit* /*killer*/) override
|
||||
{
|
||||
if (Player* player = ObjectAccessor::GetPlayer(*me, playerGuid))
|
||||
{
|
||||
if (player->GetQuestStatus(QUEST_GOGGLE_BOGGLE) == QUEST_STATUS_INCOMPLETE)
|
||||
player->FailQuest(QUEST_GOGGLE_BOGGLE);
|
||||
}
|
||||
|
||||
events.Reset();
|
||||
summons.DespawnAll();
|
||||
|
||||
eventActive = false;
|
||||
returning = false;
|
||||
waitingForAmbush = false;
|
||||
|
||||
surgeDeaths = 0;
|
||||
surgeCount = 0;
|
||||
|
||||
playerGuid.Clear();
|
||||
|
||||
// The professor is the real DB spawn. His normal 300 second
|
||||
// creature respawn handles an actual death.
|
||||
}
|
||||
|
||||
void UpdateAI(uint32 diff) override
|
||||
{
|
||||
events.Update(diff);
|
||||
|
||||
while (uint32 eventId = events.ExecuteEvent())
|
||||
{
|
||||
switch (eventId)
|
||||
{
|
||||
case EVENT_CHECK_PLAYER:
|
||||
{
|
||||
if (!eventActive)
|
||||
break;
|
||||
|
||||
Player* player = ObjectAccessor::GetPlayer(*me, playerGuid);
|
||||
|
||||
// Logout: clean up and return home. The player cannot
|
||||
// be failed while no Player object exists.
|
||||
if (!player)
|
||||
{
|
||||
AbortEvent(false);
|
||||
break;
|
||||
}
|
||||
|
||||
// Quest was abandoned, rewarded or otherwise removed.
|
||||
if (player->GetQuestStatus(QUEST_GOGGLE_BOGGLE) != QUEST_STATUS_INCOMPLETE)
|
||||
{
|
||||
AbortEvent(false);
|
||||
break;
|
||||
}
|
||||
|
||||
// Death or excessive distance means a failed escort.
|
||||
if (!player->IsAlive() || !player->IsWithinDistInMap(me, 120.0f))
|
||||
{
|
||||
AbortEvent(true);
|
||||
break;
|
||||
}
|
||||
|
||||
events.ScheduleEvent(EVENT_CHECK_PLAYER, 1000);
|
||||
break;
|
||||
}
|
||||
|
||||
case EVENT_TIMEOUT:
|
||||
{
|
||||
if (eventActive)
|
||||
AbortEvent(true);
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!UpdateVictim())
|
||||
return;
|
||||
|
||||
DoMeleeAttackIfReady();
|
||||
}
|
||||
};
|
||||
|
||||
CreatureAI* GetAI(Creature* creature) const override
|
||||
{
|
||||
return new npc_professor_phizzlethorpeAI(creature);
|
||||
}
|
||||
};
|
||||
|
||||
void AddSC_arathi_highlands()
|
||||
{
|
||||
new npc_professor_phizzlethorpe();
|
||||
}
|
||||
@@ -0,0 +1,646 @@
|
||||
/*
|
||||
* Vcore - Arathi Highlands
|
||||
* Quest 26050: Goggle Boggle
|
||||
*/
|
||||
|
||||
#include "ScriptMgr.h"
|
||||
#include "ScriptedCreature.h"
|
||||
#include "Player.h"
|
||||
#include "ObjectAccessor.h"
|
||||
#include "MotionMaster.h"
|
||||
#include "TemporarySummon.h"
|
||||
|
||||
enum PhizzlethorpeData
|
||||
{
|
||||
QUEST_GOGGLE_BOGGLE = 26050,
|
||||
|
||||
NPC_VENGEFUL_SURGE = 2776,
|
||||
|
||||
POINT_OUTBOUND_1 = 1,
|
||||
POINT_CAVE_OUTSIDE = 2,
|
||||
POINT_CAVE_INSIDE = 3,
|
||||
POINT_STONE = 4,
|
||||
POINT_RETURN_CAVE_IN = 5,
|
||||
POINT_RETURN_CAVE_OUT = 6,
|
||||
POINT_RETURN_1 = 7,
|
||||
POINT_RETURN_HOME = 8,
|
||||
|
||||
EVENT_START_MOVING = 1,
|
||||
EVENT_ENTER_CAVE = 2,
|
||||
EVENT_START_WORKING = 3,
|
||||
EVENT_SPAWN_AMBUSH = 4,
|
||||
EVENT_ALMOST_DONE = 5,
|
||||
EVENT_BEGIN_RETURN = 6,
|
||||
EVENT_FINISH_QUEST = 7,
|
||||
EVENT_CHECK_PLAYER = 8,
|
||||
EVENT_TIMEOUT = 9
|
||||
};
|
||||
|
||||
static Position const PhizzlethorpeStart =
|
||||
{
|
||||
-2066.46f, -2085.97f, 8.92410f, 0.0f
|
||||
};
|
||||
|
||||
static Position const PhizzlethorpePoint1 =
|
||||
{
|
||||
-2078.255371f, -2092.058105f, 9.744357f, 0.0f
|
||||
};
|
||||
|
||||
static Position const PhizzlethorpeCaveOutside =
|
||||
{
|
||||
-2072.194580f, -2127.455322f, 19.688131f, 0.0f
|
||||
};
|
||||
|
||||
/*
|
||||
* Point recorded directly behind the cave threshold.
|
||||
* This prevents MovePoint from trying to cut sideways through
|
||||
* the cave wall when travelling from outside to the rune stone.
|
||||
*/
|
||||
static Position const PhizzlethorpeCaveInside =
|
||||
{
|
||||
-2069.125732f, -2129.864502f, 21.102127f, 5.300940f
|
||||
};
|
||||
|
||||
static Position const PhizzlethorpeStone =
|
||||
{
|
||||
-2043.507202f, -2153.291016f, 20.177914f, 0.0f
|
||||
};
|
||||
|
||||
static Position const SurgePosition1 =
|
||||
{
|
||||
-2040.5f, -2150.5f, 20.17f, 0.0f
|
||||
};
|
||||
|
||||
static Position const SurgePosition2 =
|
||||
{
|
||||
-2046.5f, -2155.5f, 20.17f, 0.0f
|
||||
};
|
||||
|
||||
class npc_professor_phizzlethorpe : public CreatureScript
|
||||
{
|
||||
public:
|
||||
npc_professor_phizzlethorpe() : CreatureScript("npc_professor_phizzlethorpe") { }
|
||||
|
||||
struct npc_professor_phizzlethorpeAI : public ScriptedAI
|
||||
{
|
||||
npc_professor_phizzlethorpeAI(Creature* creature)
|
||||
: ScriptedAI(creature), summons(me)
|
||||
{
|
||||
}
|
||||
|
||||
EventMap events;
|
||||
SummonList summons;
|
||||
ObjectGuid playerGuid;
|
||||
|
||||
uint8 surgeDeaths = 0;
|
||||
uint8 surgeCount = 0;
|
||||
|
||||
bool eventActive = false;
|
||||
bool returning = false;
|
||||
bool waitingForAmbush = false;
|
||||
|
||||
void ResetWorkEmote()
|
||||
{
|
||||
me->SetUInt32Value(UNIT_NPC_EMOTESTATE, 0);
|
||||
}
|
||||
|
||||
void Reset() override
|
||||
{
|
||||
/*
|
||||
* Combat evade/reset can happen during an active escort.
|
||||
* Do not destroy the running quest state in that case.
|
||||
*/
|
||||
if (eventActive)
|
||||
return;
|
||||
|
||||
events.Reset();
|
||||
summons.DespawnAll();
|
||||
|
||||
playerGuid.Clear();
|
||||
|
||||
surgeDeaths = 0;
|
||||
surgeCount = 0;
|
||||
|
||||
returning = false;
|
||||
waitingForAmbush = false;
|
||||
|
||||
ResetWorkEmote();
|
||||
}
|
||||
|
||||
Player* GetEscortPlayer()
|
||||
{
|
||||
if (playerGuid.IsEmpty())
|
||||
return nullptr;
|
||||
|
||||
return ObjectAccessor::GetPlayer(*me, playerGuid);
|
||||
}
|
||||
|
||||
void StartEvent(Player* player)
|
||||
{
|
||||
if (!player || eventActive)
|
||||
return;
|
||||
|
||||
eventActive = true;
|
||||
returning = false;
|
||||
waitingForAmbush = false;
|
||||
|
||||
surgeDeaths = 0;
|
||||
surgeCount = 0;
|
||||
|
||||
playerGuid = player->GetGUID();
|
||||
|
||||
events.Reset();
|
||||
summons.DespawnAll();
|
||||
|
||||
me->CombatStop(true);
|
||||
ResetWorkEmote();
|
||||
|
||||
/*
|
||||
* Outbound trip is deliberately walked.
|
||||
*/
|
||||
me->SetWalk(true);
|
||||
|
||||
/*
|
||||
* creature_text group 0:
|
||||
* "Ok, $n. Follow me to the cave..."
|
||||
*/
|
||||
Talk(0, player);
|
||||
|
||||
/*
|
||||
* Give the opening line time to breathe before moving.
|
||||
*/
|
||||
events.ScheduleEvent(EVENT_START_MOVING, 3000);
|
||||
|
||||
events.ScheduleEvent(EVENT_CHECK_PLAYER, 1000);
|
||||
events.ScheduleEvent(EVENT_TIMEOUT, 300000);
|
||||
}
|
||||
|
||||
void AbortEvent(bool failQuest)
|
||||
{
|
||||
Player* player = GetEscortPlayer();
|
||||
|
||||
if (failQuest && player &&
|
||||
player->GetQuestStatus(QUEST_GOGGLE_BOGGLE) == QUEST_STATUS_INCOMPLETE)
|
||||
{
|
||||
player->FailQuest(QUEST_GOGGLE_BOGGLE);
|
||||
}
|
||||
|
||||
events.Reset();
|
||||
summons.DespawnAll();
|
||||
|
||||
ResetWorkEmote();
|
||||
|
||||
eventActive = false;
|
||||
returning = false;
|
||||
waitingForAmbush = false;
|
||||
|
||||
surgeDeaths = 0;
|
||||
surgeCount = 0;
|
||||
|
||||
playerGuid.Clear();
|
||||
|
||||
me->CombatStop(true);
|
||||
me->GetMotionMaster()->Clear();
|
||||
|
||||
/*
|
||||
* Do NOT despawn the professor.
|
||||
* He is the real questgiver spawn and has a long DB respawn.
|
||||
*/
|
||||
me->SetWalk(false);
|
||||
me->GetMotionMaster()->MoveTargetedHome();
|
||||
}
|
||||
|
||||
void AmbushFinished()
|
||||
{
|
||||
if (!eventActive || !waitingForAmbush)
|
||||
return;
|
||||
|
||||
waitingForAmbush = false;
|
||||
|
||||
/*
|
||||
* Stop the "working on the goggles" animation now that
|
||||
* the extraction is finished.
|
||||
*/
|
||||
ResetWorkEmote();
|
||||
|
||||
me->CombatStop(true);
|
||||
|
||||
/*
|
||||
* Small pause after the last attacker dies.
|
||||
*/
|
||||
events.ScheduleEvent(EVENT_BEGIN_RETURN, 2000);
|
||||
}
|
||||
|
||||
void FinishEvent()
|
||||
{
|
||||
Player* player = GetEscortPlayer();
|
||||
|
||||
if (player &&
|
||||
player->GetQuestStatus(QUEST_GOGGLE_BOGGLE) == QUEST_STATUS_INCOMPLETE)
|
||||
{
|
||||
/*
|
||||
* creature_text group 9:
|
||||
* Doctor Draxlegauge will give you further instructions...
|
||||
*/
|
||||
Talk(9, player);
|
||||
|
||||
player->GroupEventHappens(QUEST_GOGGLE_BOGGLE, me);
|
||||
}
|
||||
|
||||
events.Reset();
|
||||
summons.DespawnAll();
|
||||
|
||||
ResetWorkEmote();
|
||||
|
||||
eventActive = false;
|
||||
returning = false;
|
||||
waitingForAmbush = false;
|
||||
|
||||
surgeDeaths = 0;
|
||||
surgeCount = 0;
|
||||
|
||||
playerGuid.Clear();
|
||||
|
||||
me->CombatStop(true);
|
||||
me->SetWalk(false);
|
||||
}
|
||||
|
||||
void QuestAccept(Player* player, Quest const* quest) override
|
||||
{
|
||||
if (quest->GetQuestId() == QUEST_GOGGLE_BOGGLE)
|
||||
StartEvent(player);
|
||||
}
|
||||
|
||||
void MovementInform(uint32 type, uint32 id) override
|
||||
{
|
||||
if (type != POINT_MOTION_TYPE || !eventActive)
|
||||
return;
|
||||
|
||||
switch (id)
|
||||
{
|
||||
case POINT_OUTBOUND_1:
|
||||
me->GetMotionMaster()->MovePoint(
|
||||
POINT_CAVE_OUTSIDE,
|
||||
PhizzlethorpeCaveOutside);
|
||||
break;
|
||||
|
||||
case POINT_CAVE_OUTSIDE:
|
||||
{
|
||||
/*
|
||||
* creature_text group 1:
|
||||
* "I discovered this cave on our first day here..."
|
||||
*/
|
||||
if (Player* player = GetEscortPlayer())
|
||||
Talk(1, player);
|
||||
|
||||
/*
|
||||
* Five-second pause outside before entering.
|
||||
*/
|
||||
events.ScheduleEvent(EVENT_ENTER_CAVE, 5000);
|
||||
break;
|
||||
}
|
||||
|
||||
case POINT_CAVE_INSIDE:
|
||||
me->GetMotionMaster()->MovePoint(
|
||||
POINT_STONE,
|
||||
PhizzlethorpeStone);
|
||||
break;
|
||||
|
||||
case POINT_STONE:
|
||||
{
|
||||
/*
|
||||
* creature_text group 2:
|
||||
* Player is told to defend the professor.
|
||||
*/
|
||||
if (Player* player = GetEscortPlayer())
|
||||
Talk(2, player);
|
||||
|
||||
/*
|
||||
* He studies/prepares at the stone for five seconds.
|
||||
*/
|
||||
events.ScheduleEvent(EVENT_START_WORKING, 5000);
|
||||
break;
|
||||
}
|
||||
|
||||
case POINT_RETURN_CAVE_IN:
|
||||
me->GetMotionMaster()->MovePoint(
|
||||
POINT_RETURN_CAVE_OUT,
|
||||
PhizzlethorpeCaveOutside);
|
||||
break;
|
||||
|
||||
case POINT_RETURN_CAVE_OUT:
|
||||
{
|
||||
/*
|
||||
* creature_text group 7:
|
||||
* "Phew! Glad to be back from that creepy cave."
|
||||
*/
|
||||
if (Player* player = GetEscortPlayer())
|
||||
Talk(7, player);
|
||||
|
||||
me->GetMotionMaster()->MovePoint(
|
||||
POINT_RETURN_1,
|
||||
PhizzlethorpePoint1);
|
||||
break;
|
||||
}
|
||||
|
||||
case POINT_RETURN_1:
|
||||
me->GetMotionMaster()->MovePoint(
|
||||
POINT_RETURN_HOME,
|
||||
PhizzlethorpeStart);
|
||||
break;
|
||||
|
||||
case POINT_RETURN_HOME:
|
||||
{
|
||||
/*
|
||||
* creature_text group 8:
|
||||
* emote about handing the glowing goggles over.
|
||||
*/
|
||||
if (Player* player = GetEscortPlayer())
|
||||
Talk(8, player);
|
||||
|
||||
/*
|
||||
* Give the final emote a moment before the last line
|
||||
* and quest completion.
|
||||
*/
|
||||
events.ScheduleEvent(EVENT_FINISH_QUEST, 2000);
|
||||
break;
|
||||
}
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void JustSummoned(Creature* summon) override
|
||||
{
|
||||
summons.Summon(summon);
|
||||
|
||||
if (summon->GetEntry() == NPC_VENGEFUL_SURGE)
|
||||
{
|
||||
++surgeCount;
|
||||
|
||||
/*
|
||||
* The player is supposed to protect Phizzlethorpe.
|
||||
*/
|
||||
summon->AI()->AttackStart(me);
|
||||
}
|
||||
}
|
||||
|
||||
void SummonedCreatureDies(Creature* summon, Unit* /*killer*/) override
|
||||
{
|
||||
if (!eventActive ||
|
||||
!waitingForAmbush ||
|
||||
summon->GetEntry() != NPC_VENGEFUL_SURGE)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
++surgeDeaths;
|
||||
|
||||
if (surgeCount > 0 && surgeDeaths >= surgeCount)
|
||||
AmbushFinished();
|
||||
}
|
||||
|
||||
void JustDied(Unit* /*killer*/) override
|
||||
{
|
||||
ResetWorkEmote();
|
||||
|
||||
if (Player* player = GetEscortPlayer())
|
||||
{
|
||||
if (player->GetQuestStatus(QUEST_GOGGLE_BOGGLE) == QUEST_STATUS_INCOMPLETE)
|
||||
player->FailQuest(QUEST_GOGGLE_BOGGLE);
|
||||
}
|
||||
|
||||
events.Reset();
|
||||
summons.DespawnAll();
|
||||
|
||||
eventActive = false;
|
||||
returning = false;
|
||||
waitingForAmbush = false;
|
||||
}
|
||||
|
||||
void UpdateAI(uint32 diff) override
|
||||
{
|
||||
events.Update(diff);
|
||||
|
||||
while (uint32 eventId = events.ExecuteEvent())
|
||||
{
|
||||
switch (eventId)
|
||||
{
|
||||
case EVENT_START_MOVING:
|
||||
{
|
||||
if (!eventActive)
|
||||
break;
|
||||
|
||||
me->GetMotionMaster()->MovePoint(
|
||||
POINT_OUTBOUND_1,
|
||||
PhizzlethorpePoint1);
|
||||
break;
|
||||
}
|
||||
|
||||
case EVENT_ENTER_CAVE:
|
||||
{
|
||||
if (!eventActive || returning)
|
||||
break;
|
||||
|
||||
/*
|
||||
* Newly recorded point immediately behind the cave
|
||||
* threshold. This forces a sane crossing of the entrance.
|
||||
*/
|
||||
me->GetMotionMaster()->MovePoint(
|
||||
POINT_CAVE_INSIDE,
|
||||
PhizzlethorpeCaveInside);
|
||||
break;
|
||||
}
|
||||
|
||||
case EVENT_START_WORKING:
|
||||
{
|
||||
if (!eventActive || returning)
|
||||
break;
|
||||
|
||||
/*
|
||||
* creature_text group 3 is the visible emote text:
|
||||
* "...begins tinkering with the goggles..."
|
||||
*/
|
||||
if (Player* player = GetEscortPlayer())
|
||||
Talk(3, player);
|
||||
|
||||
/*
|
||||
* Emote 69 / STATE_USESTANDING.
|
||||
* Verified ingame for this character/model.
|
||||
*/
|
||||
me->SetUInt32Value(
|
||||
UNIT_NPC_EMOTESTATE,
|
||||
EMOTE_STATE_USE_STANDING);
|
||||
|
||||
/*
|
||||
* Let him visibly work for four seconds before
|
||||
* anything attacks.
|
||||
*/
|
||||
events.ScheduleEvent(EVENT_SPAWN_AMBUSH, 4000);
|
||||
break;
|
||||
}
|
||||
|
||||
case EVENT_SPAWN_AMBUSH:
|
||||
{
|
||||
if (!eventActive || returning)
|
||||
break;
|
||||
|
||||
waitingForAmbush = true;
|
||||
surgeDeaths = 0;
|
||||
surgeCount = 0;
|
||||
|
||||
/*
|
||||
* creature_text group 4 has two possible help lines.
|
||||
*/
|
||||
if (Player* player = GetEscortPlayer())
|
||||
Talk(4, player);
|
||||
|
||||
me->SummonCreature(
|
||||
NPC_VENGEFUL_SURGE,
|
||||
SurgePosition1,
|
||||
TEMPSUMMON_CORPSE_TIMED_DESPAWN,
|
||||
5000);
|
||||
|
||||
me->SummonCreature(
|
||||
NPC_VENGEFUL_SURGE,
|
||||
SurgePosition2,
|
||||
TEMPSUMMON_CORPSE_TIMED_DESPAWN,
|
||||
5000);
|
||||
|
||||
/*
|
||||
* Mid-event encouragement.
|
||||
*/
|
||||
events.ScheduleEvent(EVENT_ALMOST_DONE, 5000);
|
||||
|
||||
/*
|
||||
* If neither creature could be created, do not leave
|
||||
* the sole questgiver trapped forever.
|
||||
*/
|
||||
if (surgeCount == 0)
|
||||
AbortEvent(true);
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
case EVENT_ALMOST_DONE:
|
||||
{
|
||||
if (eventActive && waitingForAmbush)
|
||||
{
|
||||
/*
|
||||
* creature_text group 5:
|
||||
* "Almost done! Just a little longer!"
|
||||
*/
|
||||
if (Player* player = GetEscortPlayer())
|
||||
Talk(5, player);
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
case EVENT_BEGIN_RETURN:
|
||||
{
|
||||
if (!eventActive)
|
||||
break;
|
||||
|
||||
returning = true;
|
||||
waitingForAmbush = false;
|
||||
|
||||
ResetWorkEmote();
|
||||
|
||||
/*
|
||||
* creature_text group 6:
|
||||
* "I've done it!... Let's get out of here!"
|
||||
*/
|
||||
if (Player* player = GetEscortPlayer())
|
||||
Talk(6, player);
|
||||
|
||||
/*
|
||||
* Return trip may be faster than the deliberate
|
||||
* outbound walk.
|
||||
*/
|
||||
me->SetWalk(false);
|
||||
|
||||
me->GetMotionMaster()->MovePoint(
|
||||
POINT_RETURN_CAVE_IN,
|
||||
PhizzlethorpeCaveInside);
|
||||
break;
|
||||
}
|
||||
|
||||
case EVENT_FINISH_QUEST:
|
||||
{
|
||||
if (eventActive)
|
||||
FinishEvent();
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
case EVENT_CHECK_PLAYER:
|
||||
{
|
||||
if (!eventActive)
|
||||
break;
|
||||
|
||||
Player* player = GetEscortPlayer();
|
||||
|
||||
/*
|
||||
* Logout / object no longer available:
|
||||
* restore professor without artificially failing
|
||||
* an offline player's quest.
|
||||
*/
|
||||
if (!player)
|
||||
{
|
||||
AbortEvent(false);
|
||||
break;
|
||||
}
|
||||
|
||||
/*
|
||||
* Player abandoned or otherwise no longer has the
|
||||
* quest active.
|
||||
*/
|
||||
if (player->GetQuestStatus(QUEST_GOGGLE_BOGGLE) != QUEST_STATUS_INCOMPLETE)
|
||||
{
|
||||
AbortEvent(false);
|
||||
break;
|
||||
}
|
||||
|
||||
/*
|
||||
* Actual escort failure.
|
||||
*/
|
||||
if (!player->IsAlive() || me->GetDistance(player) > 120.0f)
|
||||
{
|
||||
AbortEvent(true);
|
||||
break;
|
||||
}
|
||||
|
||||
events.ScheduleEvent(EVENT_CHECK_PLAYER, 1000);
|
||||
break;
|
||||
}
|
||||
|
||||
case EVENT_TIMEOUT:
|
||||
{
|
||||
if (eventActive)
|
||||
AbortEvent(true);
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (UpdateVictim())
|
||||
DoMeleeAttackIfReady();
|
||||
}
|
||||
};
|
||||
|
||||
CreatureAI* GetAI(Creature* creature) const override
|
||||
{
|
||||
return new npc_professor_phizzlethorpeAI(creature);
|
||||
}
|
||||
};
|
||||
|
||||
void AddSC_arathi_highlands()
|
||||
{
|
||||
new npc_professor_phizzlethorpe();
|
||||
}
|
||||
@@ -222,7 +222,7 @@ void AddSC_boss_zanzil();
|
||||
void AddSC_zulgurub();
|
||||
void AddSC_instance_zulgurub();
|
||||
//void AddSC_alterac_mountains();
|
||||
//void AddSC_arathi_highlands();
|
||||
void AddSC_arathi_highlands();
|
||||
void AddSC_blasted_lands();
|
||||
void AddSC_dun_morogh();
|
||||
void AddSC_dun_morogh_area_coldridge_valley();
|
||||
@@ -454,7 +454,7 @@ void AddEasternKingdomsScripts()
|
||||
AddSC_zulgurub();
|
||||
AddSC_instance_zulgurub();
|
||||
//AddSC_alterac_mountains();
|
||||
//AddSC_arathi_highlands();
|
||||
AddSC_arathi_highlands();
|
||||
AddSC_blasted_lands();
|
||||
AddSC_dun_morogh();
|
||||
AddSC_dun_morogh_area_coldridge_valley();
|
||||
|
||||
@@ -417,11 +417,6 @@ class item_generic_limit_chance_above_60 : public ItemScript
|
||||
# item_horde_adventurer_horn
|
||||
#####*/
|
||||
|
||||
enum HordeAdventurerHorn
|
||||
{
|
||||
NPC_HORDE_ADVENTURER = 900001
|
||||
};
|
||||
|
||||
class item_horde_adventurer_horn : public ItemScript
|
||||
{
|
||||
public:
|
||||
@@ -429,35 +424,53 @@ public:
|
||||
|
||||
bool OnUse(Player* player, Item* item, SpellCastTargets const& /*targets*/) override
|
||||
{
|
||||
TC_LOG_INFO("scripts", "Horde adventureer horn OnUse triggered by player %s (item %u)", player->GetName().c_str(), item->GetEntry());
|
||||
// The horn may not be used during combat.
|
||||
TC_LOG_INFO(
|
||||
"scripts",
|
||||
"[VianoHorn] OnUse player='%s' item=%u",
|
||||
player->GetName().c_str(),
|
||||
item->GetEntry());
|
||||
|
||||
// Horns may not be used during combat.
|
||||
if (player->IsInCombat())
|
||||
{
|
||||
player->SendEquipError(EQUIP_ERR_CLIENT_LOCKED_OUT, item, nullptr);
|
||||
TC_LOG_INFO("scripts",
|
||||
"[VianoHorn] rejected: player is in combat");
|
||||
|
||||
player->SendEquipError(
|
||||
EQUIP_ERR_CLIENT_LOCKED_OUT,
|
||||
item,
|
||||
nullptr);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
// The horn may only be used inside a dungeon instance.
|
||||
// Horns may only be used inside dungeon instances.
|
||||
if (!player->GetMap()->IsDungeon())
|
||||
{
|
||||
player->SendEquipError(EQUIP_ERR_CLIENT_LOCKED_OUT, item, nullptr);
|
||||
TC_LOG_INFO("scripts",
|
||||
"[VianoHorn] rejected: player is not inside a dungeon");
|
||||
|
||||
player->SendEquipError(
|
||||
EQUIP_ERR_CLIENT_LOCKED_OUT,
|
||||
item,
|
||||
nullptr);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
player->SummonCreature(
|
||||
NPC_HORDE_ADVENTURER,
|
||||
player->GetPositionX() + 2.0f,
|
||||
player->GetPositionY(),
|
||||
player->GetPositionZ(),
|
||||
player->GetOrientation(),
|
||||
TEMPSUMMON_MANUAL_DESPAWN,
|
||||
0
|
||||
);
|
||||
TC_LOG_INFO(
|
||||
"scripts",
|
||||
"[VianoHorn] prechecks passed; allowing normal item spell processing");
|
||||
|
||||
return true;
|
||||
// IMPORTANT:
|
||||
// false means the ItemScript does not consume/suppress the normal item use.
|
||||
// The item's SpellID1 (120000-120003) may now run through the regular
|
||||
// Trinity spell lifecycle.
|
||||
return false;
|
||||
}
|
||||
};
|
||||
}
|
||||
};
|
||||
}// namespace ItemScripts
|
||||
|
||||
|
||||
void AddSC_item_scripts()
|
||||
{
|
||||
|
||||
Reference in new issue
Block a user