whole lot of fixes

This commit is contained in:
Nico committed 2026-10-01 00:23:36 +02:00
1 parent fec36215f6
commit 3cf7e70283
14 files changed
+3174 -83

No files matched your search

+53 -20
View File
@@ -15837,15 +15837,26 @@ void Player::KilledMonsterCredit(uint32 entry, ObjectGuid guid /*= ObjectGuid::E
uint16 curkillcount = q_status.CreatureOrGOCount[j];
if (curkillcount < reqkillcount)
{
q_status.CreatureOrGOCount[j] = curkillcount + addkillcount;
uint16 newCount = curkillcount + addkillcount;
m_QuestStatusSave[questid] = QUEST_DEFAULT_SAVE_TYPE;
q_status.CreatureOrGOCount[j] = newCount;
uint16 log_slot = FindQuestSlot(questid);
if (log_slot < MAX_QUEST_LOG_SIZE)
SetQuestSlotCounter(log_slot, j, curkillcount + addkillcount);
m_QuestStatusSave[questid] = QUEST_DEFAULT_SAVE_TYPE;
SendQuestUpdateAddCredit(qInfo, guid, j, curkillcount + addkillcount);
uint16 log_slot = FindQuestSlot(questid);
if (log_slot < MAX_QUEST_LOG_SIZE)
SetQuestSlotCounter(log_slot, j, newCount);
sScriptMgr->OnQuestObjectiveCredit(
this,
questid,
j,
qInfo->RequiredNpcOrGo[j],
curkillcount,
newCount,
guid);
SendQuestUpdateAddCredit(qInfo, guid, j, newCount);
}
if (CanCompleteQuest(questid))
CompleteQuest(questid);
@@ -15949,17 +15960,28 @@ void Player::KillCreditGO(uint32 entry, ObjectGuid guid)
uint32 reqCastCount = qInfo->RequiredNpcOrGoCount[j];
uint16 curCastCount = q_status.CreatureOrGOCount[j];
if (curCastCount < reqCastCount)
{
q_status.CreatureOrGOCount[j] = curCastCount + addCastCount;
{
uint16 newCount = curCastCount + addCastCount;
m_QuestStatusSave[questid] = QUEST_DEFAULT_SAVE_TYPE;
q_status.CreatureOrGOCount[j] = newCount;
uint16 log_slot = FindQuestSlot(questid);
if (log_slot < MAX_QUEST_LOG_SIZE)
SetQuestSlotCounter(log_slot, j, curCastCount + addCastCount);
m_QuestStatusSave[questid] = QUEST_DEFAULT_SAVE_TYPE;
SendQuestUpdateAddCredit(qInfo, guid, j, curCastCount + addCastCount);
}
uint16 log_slot = FindQuestSlot(questid);
if (log_slot < MAX_QUEST_LOG_SIZE)
SetQuestSlotCounter(log_slot, j, newCount);
sScriptMgr->OnQuestObjectiveCredit(
this,
questid,
j,
qInfo->RequiredNpcOrGo[j],
curCastCount,
newCount,
guid);
SendQuestUpdateAddCredit(qInfo, guid, j, newCount);
}
if (CanCompleteQuest(questid))
CompleteQuest(questid);
@@ -16011,15 +16033,26 @@ void Player::TalkedToCreature(uint32 entry, ObjectGuid guid)
uint16 curTalkCount = q_status.CreatureOrGOCount[j];
if (curTalkCount < reqTalkCount)
{
q_status.CreatureOrGOCount[j] = curTalkCount + addTalkCount;
uint16 newCount = curTalkCount + addTalkCount;
m_QuestStatusSave[questid] = QUEST_DEFAULT_SAVE_TYPE;
q_status.CreatureOrGOCount[j] = newCount;
uint16 log_slot = FindQuestSlot(questid);
if (log_slot < MAX_QUEST_LOG_SIZE)
SetQuestSlotCounter(log_slot, j, curTalkCount + addTalkCount);
m_QuestStatusSave[questid] = QUEST_DEFAULT_SAVE_TYPE;
SendQuestUpdateAddCredit(qInfo, guid, j, curTalkCount + addTalkCount);
uint16 log_slot = FindQuestSlot(questid);
if (log_slot < MAX_QUEST_LOG_SIZE)
SetQuestSlotCounter(log_slot, j, newCount);
sScriptMgr->OnQuestObjectiveCredit(
this,
questid,
j,
qInfo->RequiredNpcOrGo[j],
curTalkCount,
newCount,
guid);
SendQuestUpdateAddCredit(qInfo, guid, j, newCount);
}
if (CanCompleteQuest(questid))
CompleteQuest(questid);
+18 -1
View File
@@ -1952,7 +1952,24 @@ void ScriptMgr::OnQuestStatusChange(Player* player, uint32 questId)
{
FOREACH_SCRIPT(PlayerScript)->OnQuestStatusChange(player, questId);
}
void ScriptMgr::OnQuestObjectiveCredit(
Player* player,
uint32 questId,
uint8 objectiveIndex,
int32 objectiveEntry,
uint16 oldCount,
uint16 newCount,
ObjectGuid sourceGuid)
{
FOREACH_SCRIPT(PlayerScript)->OnQuestObjectiveCredit(
player,
questId,
objectiveIndex,
objectiveEntry,
oldCount,
newCount,
sourceGuid);
}
void ScriptMgr::OnPlayerRepop(Player* player)
{
FOREACH_SCRIPT(PlayerScript)->OnPlayerRepop(player);
+17
View File
@@ -708,6 +708,15 @@ class TC_GAME_API PlayerScript : public ScriptObject
// Called after a player's quest status has been changed
virtual void OnQuestStatusChange(Player* /*player*/, uint32 /*questId*/) { }
// Called after a creature/gameobject quest objective counter has changed
virtual void OnQuestObjectiveCredit(
Player* /*player*/,
uint32 /*questId*/,
uint8 /*objectiveIndex*/,
int32 /*objectiveEntry*/,
uint16 /*oldCount*/,
uint16 /*newCount*/,
ObjectGuid /*sourceGuid*/) { }
// Called when a player presses release when he died
virtual void OnPlayerRepop(Player* /*player*/) { }
@@ -1035,6 +1044,14 @@ class TC_GAME_API ScriptMgr
void OnPlayerBindToInstance(Player* player, Difficulty difficulty, uint32 mapid, bool permanent, uint8 extendState);
void OnPlayerUpdateZone(Player* player, uint32 newZone, uint32 newArea);
void OnQuestStatusChange(Player* player, uint32 questId);
void OnQuestObjectiveCredit(
Player* player,
uint32 questId,
uint8 objectiveIndex,
int32 objectiveEntry,
uint16 oldCount,
uint16 newCount,
ObjectGuid sourceGuid);
void OnPlayerRepop(Player* player);
public: /* AccountScript */
@@ -20,6 +20,7 @@
// VIANO_ADVENTURERS
void AddSC_viano_adventurers();
void AddSC_viano_bot_commands();
// Viano Server diagnostics
void AddSC_vcore_diagnostics();
@@ -29,6 +30,7 @@ void AddSC_vcore_diagnostics();
void AddCustomScripts()
{
AddSC_viano_adventurers();
AddSC_viano_bot_commands();
AddSC_vcore_diagnostics();
}
@@ -0,0 +1,34 @@
/*
* 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();
}
+345 -20
View File
@@ -1,5 +1,5 @@
/*
* Vcore Diagnostics Server v0.5
* Vcore Diagnostics Server v0.6.1
*
* Target: The Cataclysm Preservation Project / Vcore API line.
*
@@ -190,6 +190,139 @@ namespace VCoreDiagnostics
static QueryResult QuerySync(std::string const& sql);
static bool IsSafeSqlIdentifier(std::string const& value)
{
if (value.empty())
return false;
for (char c : value)
{
bool alpha = (c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z');
bool digit = c >= '0' && c <= '9';
if (!alpha && !digit && c != '_')
return false;
}
return true;
}
static std::optional<std::string> PrimaryKeyColumn(
std::string const& database,
std::string const& table)
{
std::string safeDatabase = database;
std::string safeTable = table;
WorldDatabase.EscapeString(safeDatabase);
WorldDatabase.EscapeString(safeTable);
std::ostringstream sql;
sql << "SELECT COLUMN_NAME FROM information_schema.COLUMNS "
<< "WHERE TABLE_SCHEMA='" << safeDatabase << "' "
<< "AND TABLE_NAME='" << safeTable << "' "
<< "AND COLUMN_KEY='PRI' "
<< "ORDER BY ORDINAL_POSITION LIMIT 1";
QueryResult result = QuerySync(sql.str());
if (!result)
return std::nullopt;
std::string column = (*result)[0].GetString();
if (!IsSafeSqlIdentifier(column))
return std::nullopt;
return column;
}
static std::optional<std::string> DisplayTextColumn(
std::string const& database,
std::string const& table)
{
std::string safeDatabase = database;
std::string safeTable = table;
WorldDatabase.EscapeString(safeDatabase);
WorldDatabase.EscapeString(safeTable);
std::ostringstream sql;
sql << "SELECT COLUMN_NAME FROM information_schema.COLUMNS "
<< "WHERE TABLE_SCHEMA='" << safeDatabase << "' "
<< "AND TABLE_NAME='" << safeTable << "' "
<< "AND DATA_TYPE IN ('char','varchar','tinytext','text','mediumtext','longtext') "
<< "ORDER BY ORDINAL_POSITION LIMIT 1";
QueryResult result = QuerySync(sql.str());
if (!result)
return std::nullopt;
std::string column = (*result)[0].GetString();
if (!IsSafeSqlIdentifier(column))
return std::nullopt;
return column;
}
static bool DatabaseEntityExists(
std::string const& database,
std::string const& table,
uint64 id)
{
if (!id)
return false;
auto keyColumn = PrimaryKeyColumn(database, table);
if (!keyColumn)
return false;
std::ostringstream sql;
sql << "SELECT 1 FROM `" << database << "`.`" << table << "` "
<< "WHERE `" << *keyColumn << "`=" << id << " LIMIT 1";
return bool(QuerySync(sql.str()));
}
static bool ValidateDiagnosticFocus(
Player* player,
std::string const& focusType,
uint64 focusId)
{
if (focusType.empty())
return focusId == 0;
if (focusType == "quest")
return focusId <= 0xFFFFFFFFULL &&
IsQuestInPlayerLog(player, uint32(focusId));
if (focusType == "world_event")
{
// A tester may only select events explicitly exposed through the
// Vcore diagnostic event catalog.
std::ostringstream sql;
sql << "SELECT 1 "
<< "FROM vcore_dev.diagnostic_event_catalog c "
<< "INNER JOIN cata_world.game_event g "
<< "ON g.eventEntry=c.event_entry "
<< "WHERE c.enabled=1 "
<< "AND c.event_entry=" << focusId << " "
<< "LIMIT 1";
return bool(QuerySync(sql.str()));
}
if (focusType == "creature")
return DatabaseEntityExists("cata_world", "creature_template", focusId);
if (focusType == "gameobject")
return DatabaseEntityExists("cata_world", "gameobject_template", focusId);
if (focusType == "zone")
return player && focusId == player->GetZoneId();
if (focusType == "other")
return focusId == 0;
return false;
}
static QueryResult QuerySync(std::string const& sql);
static std::string QuestTitle(uint32 questId)
{
if (!questId)
@@ -293,9 +426,20 @@ namespace VCoreDiagnostics
first = false;
out << "{";
out << "\"id\":" << questId << ",";
out << "\"status\":" << uint32(player->GetQuestStatus(questId));
out << "}";
out << "\"id\":" << questId << ",";
out << "\"status\":" << uint32(player->GetQuestStatus(questId)) << ",";
out << "\"objective_counters\":[";
for (uint8 counter = 0; counter < QUEST_OBJECTIVES_COUNT; ++counter)
{
if (counter)
out << ",";
out << player->GetQuestSlotCounter(slot, counter);
}
out << "]";
out << "}";
}
out << "]";
@@ -566,11 +710,37 @@ namespace VCoreDiagnostics
static std::optional<uint64> StartReproductionForReport(
Player* player,
uint64 reportId,
uint32 questId = 0)
uint32 questId = 0,
std::string diagnosticFocusType = {},
uint64 diagnosticFocusId = 0)
{
if (!player || !reportId)
return std::nullopt;
// Backward compatibility: the old tester protocol supplied only questId.
// Treat that as an explicit quest focus so existing AddOn versions keep
// producing structured diagnostic context.
if (diagnosticFocusType.empty() && questId)
{
diagnosticFocusType = "quest";
diagnosticFocusId = questId;
}
if (diagnosticFocusType == "quest")
{
if (!diagnosticFocusId || diagnosticFocusId > 0xFFFFFFFFULL)
return std::nullopt;
uint32 focusQuestId = uint32(diagnosticFocusId);
if (questId && questId != focusQuestId)
return std::nullopt;
questId = focusQuestId;
}
if (!ValidateDiagnosticFocus(player, diagnosticFocusType, diagnosticFocusId))
return std::nullopt;
if (questId && !IsQuestInPlayerLog(player, questId))
return std::nullopt;
@@ -605,8 +775,8 @@ namespace VCoreDiagnostics
std::ostringstream sql;
sql << "INSERT INTO vcore_dev.bug_reproductions "
<< "(session_token, bug_id, report_id, quest_id, tester_account_id, tester_character_guid, "
<< "tester_character_name, result, map_id, zone_id, area_id, "
<< "(session_token, bug_id, report_id, quest_id, diagnostic_focus_type, diagnostic_focus_id, "
<< "tester_account_id, tester_character_guid, tester_character_name, result, map_id, zone_id, area_id, "
<< "position_x, position_y, position_z, orientation, started_at, created_at) "
<< "VALUES ('"
<< safeToken << "', ";
@@ -624,6 +794,21 @@ namespace VCoreDiagnostics
else
sql << "NULL";
std::string safeFocusType = diagnosticFocusType;
WorldDatabase.EscapeString(safeFocusType);
sql << ", ";
if (!diagnosticFocusType.empty())
sql << "'" << safeFocusType << "'";
else
sql << "NULL";
sql << ", ";
if (diagnosticFocusId)
sql << diagnosticFocusId;
else
sql << "NULL";
sql << ", "
<< player->GetSession()->GetAccountId() << ", "
<< player->GetGUID().GetCounter() << ", '"
@@ -654,11 +839,24 @@ namespace VCoreDiagnostics
<< "WHERE report_id=" << reportId;
ExecuteSync(updateReport.str());
std::ostringstream startData;
startData << "{\"diagnostic_focus\":";
if (!diagnosticFocusType.empty())
{
startData << "{\"type\":\"" << JsonEscape(diagnosticFocusType)
<< "\",\"id\":" << diagnosticFocusId << "}";
}
else
{
startData << "null";
}
startData << ",\"snapshot\":" << BuildServerSnapshot(player) << "}";
InsertReproductionEvent(
*id,
"server",
"reproduction_started",
"{\"snapshot\":" + BuildServerSnapshot(player) + "}");
startData.str());
return id;
}
@@ -913,9 +1111,16 @@ namespace VCoreDiagnostics
static std::optional<uint64> StartTesterReproduction(
Player* player,
uint64 reportId,
uint32 questId = 0)
uint32 questId = 0,
std::string diagnosticFocusType = {},
uint64 diagnosticFocusId = 0)
{
return StartReproductionForReport(player, reportId, questId);
return StartReproductionForReport(
player,
reportId,
questId,
std::move(diagnosticFocusType),
diagnosticFocusId);
}
static bool AddTesterReproductionNote(
@@ -994,6 +1199,45 @@ namespace VCoreDiagnostics
SendAddonResponse(player, requestId, response.str());
}
static void SendWorldEventList(
Player* player,
std::string const& requestId)
{
// Tester-facing event selection is intentionally curated in
// vcore_dev.diagnostic_event_catalog. The authoritative game_event
// table remains the technical event source, while the catalog decides
// which events are useful diagnostic categories.
QueryResult result = QuerySync(
"SELECT c.event_entry, c.display_name "
"FROM vcore_dev.diagnostic_event_catalog c "
"INNER JOIN cata_world.game_event g "
"ON g.eventEntry=c.event_entry "
"WHERE c.enabled=1 "
"ORDER BY c.sort_order, c.display_name, c.event_entry");
std::ostringstream response;
response << "WORLD_EVENT_LIST|";
bool first = true;
if (result)
{
do
{
Field* fields = result->Fetch();
uint64 eventId = fields[0].GetUInt64();
std::string label = fields[1].GetString();
if (!first)
response << "\n";
first = false;
response << eventId << "|" << HexEncode(label);
} while (result->NextRow());
}
SendAddonResponse(player, requestId, response.str());
}
static void SendPermissions(Player* player, std::string const& requestId)
{
bool tester = HasTesterPermission(player);
@@ -1538,6 +1782,22 @@ namespace VCoreDiagnostics
return;
}
if (operation == 'V')
{
if (!HasTesterPermission(player))
{
SendAddonError(
player,
requestId,
"PERMISSION",
"Insufficient permission for tester diagnostic context.");
return;
}
SendWorldEventList(player, requestId);
return;
}
if (operation == 'R')
{
std::string severity;
@@ -1681,28 +1941,68 @@ namespace VCoreDiagnostics
{
uint32 reportId = 0;
uint32 questId = 0;
uint32 focusId = 0;
std::string focusType;
std::string reportText = payload;
std::string questText;
std::string remainder;
// Backward compatible payloads:
// old: reportId|questId
// new: reportId|questId|focusType|focusId
std::vector<std::string> fields;
std::size_t start = 0;
while (true)
{
std::size_t pos = payload.find('|', start);
if (pos == std::string::npos)
{
fields.push_back(payload.substr(start));
break;
}
if (SplitFirst(payload, '|', reportText, remainder))
questText = remainder;
fields.push_back(payload.substr(start, pos - start));
start = pos + 1;
}
if (!ParseUnsigned(reportText, reportId) || !reportId)
if (fields.empty() || fields.size() > 4 ||
!ParseUnsigned(fields[0], reportId) || !reportId)
{
SendAddonError(player, requestId, "BAD_REPORT_ID", "Invalid report ID.");
return;
}
if (!questText.empty() && !ParseUnsigned(questText, questId))
if (fields.size() >= 2 && !fields[1].empty() &&
!ParseUnsigned(fields[1], questId))
{
SendAddonError(player, requestId, "BAD_QUEST_ID", "Invalid quest ID.");
return;
}
auto reproductionId =
StartTesterReproduction(player, uint64(reportId), questId);
if (fields.size() >= 3)
focusType = fields[2];
if (fields.size() >= 4 && !fields[3].empty() &&
!ParseUnsigned(fields[3], focusId))
{
SendAddonError(player, requestId, "BAD_FOCUS_ID", "Invalid diagnostic focus ID.");
return;
}
if ((!focusType.empty() && !focusId && focusType != "other") ||
(focusType.empty() && focusId))
{
SendAddonError(
player,
requestId,
"BAD_FOCUS",
"Diagnostic focus type/ID combination is invalid.");
return;
}
auto reproductionId = StartTesterReproduction(
player,
uint64(reportId),
questId,
focusType,
uint64(focusId));
if (!reproductionId)
{
@@ -1710,7 +2010,7 @@ namespace VCoreDiagnostics
player,
requestId,
"REPRO_START_FAILED",
"Could not start reproduction. Report/quest may be unavailable.");
"Could not start reproduction. Report or diagnostic focus may be unavailable.");
return;
}
@@ -2130,6 +2430,31 @@ namespace VCoreDiagnostics
Record(player, "quest_status_changed", data.str());
}
void OnQuestObjectiveCredit(
Player* player,
uint32 questId,
uint8 objectiveIndex,
int32 objectiveEntry,
uint16 oldCount,
uint16 newCount,
ObjectGuid sourceGuid) override
{
if (!player)
return;
std::ostringstream data;
data << "{"
<< "\"quest_id\":" << questId << ","
<< "\"objective_index\":" << uint32(objectiveIndex) << ","
<< "\"objective_entry\":" << objectiveEntry << ","
<< "\"old_count\":" << oldCount << ","
<< "\"new_count\":" << newCount << ","
<< "\"source_guid\":" << sourceGuid.GetCounter()
<< "}";
Record(player, "quest_objective_credit", data.str());
}
void OnSpellCast(Player* player, Spell* spell, bool /*skipCheck*/) override
{
if (!spell || !spell->GetSpellInfo())
+78 -39
View File
@@ -329,49 +329,83 @@ namespace VianoAdventurers
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;
{
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;
// 1. An active owner victim is an explicit target instruction.
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;
}
// 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. 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;
// 2. Keep the bot's own valid current victim.
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;
// 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. 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;
// 4. Finally, defend the bot itself if something is attacking it.
for (Unit* attacker : me->getAttackers())
if (IsValidCandidate(attacker))
return attacker;
return nullptr;
}
return nullptr;
}
void UpdateAI(uint32 diff) override
{
@@ -416,9 +450,11 @@ namespace VianoAdventurers
me->UpdateLevelDependantStats();
}
if (me->GetDistance(owner) > TELEPORT_DISTANCE)
if (me->GetDistance(owner) > TELEPORT_DISTANCE && !IsActivelyEngagedInCombat())
{
me->CombatStop(true);
// 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(
@@ -450,8 +486,11 @@ namespace VianoAdventurers
me->IsInCombat() ? 1u : 0u, UnitName(afterVictim), UnitEntry(afterVictim));
}
}
else if (!me->IsInCombat() && !me->GetVictim())
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);
}
@@ -0,0 +1,477 @@
/*
* 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();
}
@@ -0,0 +1,401 @@
/*
* 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();
}
@@ -0,0 +1,400 @@
/*
* 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();
}
@@ -288,10 +288,24 @@ struct npc_worgen_runt : public ScriptedAI
_events.ScheduleEvent(EVENT_AGGRO_PLAYER, 2s);
break;
case EVENT_AGGRO_PLAYER:
if (Unit* player = ObjectAccessor::GetPlayer(*me, _playerGuid))
if (me->IsInDist(player->GetPosition(), 100.0f))
AttackStart(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;
}
@@ -0,0 +1,682 @@
/*
* 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:
if (Unit* player = ObjectAccessor::GetPlayer(*me, _playerGuid))
if (me->IsInDist(player->GetPosition(), 100.0f))
AttackStart(player);
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);
}
@@ -33,6 +33,7 @@ Script Data End */
#include "ScriptedGossip.h"
#include "SpellScript.h"
#include "TemporarySummon.h"
#include "GameObjectAI.h"
namespace Gnomeregan
{
@@ -544,6 +545,36 @@ public:
}
};
// 208325 - Extra Parachutes
enum ExtraParachutes
{
ITEM_SAFE_PARACHUTE = 60680
};
class go_gnomeregan_extra_parachutes : public GameObjectScript
{
public:
go_gnomeregan_extra_parachutes() : GameObjectScript("go_gnomeregan_extra_parachutes") { }
struct go_gnomeregan_extra_parachutesAI : public GameObjectAI
{
go_gnomeregan_extra_parachutesAI(GameObject* go) : GameObjectAI(go) { }
bool GossipHello(Player* player) override
{
if (!player->HasItemCount(60680, 1))
player->AddItem(60680, 1);
return true;
}
};
GameObjectAI* GetAI(GameObject* go) const override
{
return new go_gnomeregan_extra_parachutesAI(go);
}
};
// 12709 - Collecting Fallout
class spell_collecting_fallout : public SpellScriptLoader
{
@@ -588,6 +619,7 @@ void AddSC_gnomeregan()
using namespace Gnomeregan;
new npc_blastmaster_emi_shortfuse();
new boss_grubbis();
new go_gnomeregan_extra_parachutes();
new spell_collecting_fallout();
}
@@ -0,0 +1,618 @@
/*
* 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/>.
*/
/* Script Data Start
SDName: Gnomeregan
SDAuthor: Manuel
SD%Complete: 90%
SDComment: Some visual effects are not implemented.
Script Data End */
#include "ScriptMgr.h"
#include "GameObject.h"
#include "gnomeregan.h"
#include "InstanceScript.h"
#include "Map.h"
#include "ObjectAccessor.h"
#include "Player.h"
#include "ScriptedEscortAI.h"
#include "ScriptedGossip.h"
#include "SpellScript.h"
#include "TemporarySummon.h"
namespace Gnomeregan
{
enum BlastmasterEmi
{
SAY_BLASTMASTER_0 = 0,
SAY_BLASTMASTER_1 = 1,
SAY_BLASTMASTER_2 = 2,
SAY_BLASTMASTER_3 = 3,
SAY_BLASTMASTER_4 = 4,
SAY_BLASTMASTER_5 = 5,
SAY_BLASTMASTER_6 = 6,
SAY_BLASTMASTER_7 = 7,
SAY_BLASTMASTER_8 = 8,
SAY_BLASTMASTER_9 = 9,
SAY_BLASTMASTER_10 = 10,
SAY_BLASTMASTER_11 = 11,
SAY_BLASTMASTER_12 = 12,
SAY_BLASTMASTER_13 = 13,
SAY_BLASTMASTER_14 = 14,
SAY_BLASTMASTER_15 = 15,
SAY_BLASTMASTER_16 = 16,
SAY_BLASTMASTER_17 = 17,
SAY_BLASTMASTER_18 = 18,
SAY_BLASTMASTER_19 = 19,
SAY_GRUBBIS = 0
};
const Position SpawnPosition[] =
{
{-557.630f, -114.514f, -152.209f, 0.641f},
{-555.263f, -113.802f, -152.737f, 0.311f},
{-552.154f, -112.476f, -153.349f, 0.621f},
{-548.692f, -111.089f, -154.090f, 0.621f},
{-546.905f, -108.340f, -154.877f, 0.729f},
{-547.736f, -105.154f, -155.176f, 0.372f},
{-547.274f, -114.109f, -153.952f, 0.735f},
{-552.534f, -110.012f, -153.577f, 0.747f},
{-550.708f, -116.436f, -153.103f, 0.679f},
{-554.030f, -115.983f, -152.635f, 0.695f},
{-494.595f, -87.516f, -149.116f, 3.344f},
{-493.349f, -90.845f, -148.882f, 3.717f},
{-491.995f, -87.619f, -148.197f, 3.230f},
{-490.732f, -90.739f, -148.091f, 3.230f},
{-490.554f, -89.114f, -148.055f, 3.230f},
{-495.240f, -90.808f, -149.493f, 3.238f},
{-494.195f, -89.553f, -149.131f, 3.254f},
{-511.3304f, -139.9622f, -152.4761f, 0.7504908f},
{-510.6754f, -139.4371f, -152.6167f, 3.33359f},
{-511.8976f, -139.3562f, -152.4785f, 3.961899f}
};
class npc_blastmaster_emi_shortfuse : public CreatureScript
{
public:
npc_blastmaster_emi_shortfuse() : CreatureScript("npc_blastmaster_emi_shortfuse") { }
struct npc_blastmaster_emi_shortfuseAI : public EscortAI
{
npc_blastmaster_emi_shortfuseAI(Creature* creature) : EscortAI(creature)
{
instance = creature->GetInstanceScript();
creature->RestoreFaction();
Reset();
}
InstanceScript* instance;
uint8 uiPhase;
uint32 uiTimer;
GuidList SummonList;
GuidList GoSummonList;
void Reset() override
{
if (!HasEscortState(STATE_ESCORT_ESCORTING))
{
uiTimer = 0;
uiPhase = 0;
RestoreAll();
SummonList.clear();
GoSummonList.clear();
}
}
bool GossipSelect(Player* player, uint32 /*menuId*/, uint32 gossipListId) override
{
if (gossipListId == 0)
{
Start(true, false, player->GetGUID());
me->SetFaction(player->GetFaction());
SetData(1, 0);
player->PlayerTalkClass->SendCloseGossip();
}
return false;
}
void NextStep(uint32 uiTimerStep, bool bNextStep = true, uint8 uiPhaseStep = 0)
{
uiTimer = uiTimerStep;
if (bNextStep)
++uiPhase;
else
uiPhase = uiPhaseStep;
}
void CaveDestruction(bool isRight)
{
if (GoSummonList.empty())
return;
for (GuidList::const_iterator itr = GoSummonList.begin(); itr != GoSummonList.end(); ++itr)
{
if (GameObject* go = ObjectAccessor::GetGameObject(*me, *itr))
{
if (Creature* trigger = go->SummonTrigger(go->GetPositionX(), go->GetPositionY(), go->GetPositionZ(), 0, 1))
{
//visual effects are not working!
trigger->CastSpell(trigger, 11542, true);
trigger->CastSpell(trigger, 35470, true);
}
go->RemoveFromWorld();
//go->CastSpell(me, 12158); makes all die?!
}
}
if (GameObject* go = ObjectAccessor::GetGameObject(*me, instance->GetGuidData(isRight ? DATA_GO_CAVE_IN_RIGHT : DATA_GO_CAVE_IN_LEFT)))
instance->HandleGameObject(ObjectGuid::Empty, false, go);
}
void SetInFace(bool isRight)
{
if (GameObject* go = ObjectAccessor::GetGameObject(*me, instance->GetGuidData(isRight ? DATA_GO_CAVE_IN_RIGHT : DATA_GO_CAVE_IN_LEFT)))
me->SetFacingToObject(go);
}
void RestoreAll()
{
if (GameObject* go = ObjectAccessor::GetGameObject(*me, instance->GetGuidData(DATA_GO_CAVE_IN_RIGHT)))
instance->HandleGameObject(ObjectGuid::Empty, false, go);
if (GameObject* go = ObjectAccessor::GetGameObject(*me, instance->GetGuidData(DATA_GO_CAVE_IN_LEFT)))
instance->HandleGameObject(ObjectGuid::Empty, false, go);
if (!GoSummonList.empty())
for (GuidList::const_iterator itr = GoSummonList.begin(); itr != GoSummonList.end(); ++itr)
{
if (GameObject* go = ObjectAccessor::GetGameObject(*me, *itr))
go->RemoveFromWorld();
}
if (!SummonList.empty())
for (GuidList::const_iterator itr = SummonList.begin(); itr != SummonList.end(); ++itr)
{
if (Creature* summon = ObjectAccessor::GetCreature(*me, *itr))
{
summon->DespawnOrUnsummon();
}
}
}
void AggroAllPlayers(Creature* temp)
{
Map::PlayerList const& PlList = me->GetMap()->GetPlayers();
for (Map::PlayerList::const_iterator i = PlList.begin(); i != PlList.end(); ++i)
{
if (Player* player = i->GetSource())
{
if (player->IsGameMaster())
continue;
if (player->IsAlive())
AddThreat(player, 0.0f, temp);
}
}
}
void WaypointReached(uint32 waypointId, uint32 /*pathId*/) override
{
//just in case
if (GetPlayerForEscort())
if (me->GetFaction() != GetPlayerForEscort()->GetFaction())
me->SetFaction(GetPlayerForEscort()->GetFaction());
switch (waypointId)
{
case 3:
SetEscortPaused(true);
NextStep(2000, false, 3);
break;
case 7:
SetEscortPaused(true);
NextStep(2000, false, 4);
break;
case 9:
NextStep(1000, false, 8);
break;
case 10:
NextStep(25000, false, 10);
break;
case 11:
SetEscortPaused(true);
SetInFace(true);
NextStep(1000, false, 11);
break;
case 12:
NextStep(25000, false, 18);
break;
case 13:
Summon(6);
NextStep(25000, false, 19);
break;
case 14:
SetInFace(false);
Talk(SAY_BLASTMASTER_17);
SetEscortPaused(true);
NextStep(5000, false, 20);
break;
}
}
void SetData(uint32 uiI, uint32 uiValue) override
{
switch (uiI)
{
case 1:
SetEscortPaused(true);
Talk(SAY_BLASTMASTER_0);
NextStep(2000, true);
break;
case 2:
switch (uiValue)
{
case 1:
instance->SetData(TYPE_EVENT, IN_PROGRESS);
break;
case 2:
instance->SetData(TYPE_EVENT, DONE);
NextStep(5000, false, 22);
break;
}
break;
}
}
void Summon(uint8 uiCase)
{
switch (uiCase)
{
case 1:
me->SummonCreature(NPC_CAVERNDEEP_AMBUSHER, SpawnPosition[0], TEMPSUMMON_CORPSE_TIMED_DESPAWN, 1800000);
me->SummonCreature(NPC_CAVERNDEEP_AMBUSHER, SpawnPosition[1], TEMPSUMMON_CORPSE_TIMED_DESPAWN, 1800000);
me->SummonCreature(NPC_CAVERNDEEP_AMBUSHER, SpawnPosition[2], TEMPSUMMON_CORPSE_TIMED_DESPAWN, 1800000);
me->SummonCreature(NPC_CAVERNDEEP_AMBUSHER, SpawnPosition[3], TEMPSUMMON_CORPSE_TIMED_DESPAWN, 1800000);
me->SummonCreature(NPC_CAVERNDEEP_AMBUSHER, SpawnPosition[4], TEMPSUMMON_CORPSE_TIMED_DESPAWN, 1800000);
me->SummonCreature(NPC_CAVERNDEEP_AMBUSHER, SpawnPosition[5], TEMPSUMMON_CORPSE_TIMED_DESPAWN, 1800000);
me->SummonCreature(NPC_CAVERNDEEP_AMBUSHER, SpawnPosition[6], TEMPSUMMON_CORPSE_TIMED_DESPAWN, 1800000);
me->SummonCreature(NPC_CAVERNDEEP_AMBUSHER, SpawnPosition[7], TEMPSUMMON_CORPSE_TIMED_DESPAWN, 1800000);
me->SummonCreature(NPC_CAVERNDEEP_AMBUSHER, SpawnPosition[8], TEMPSUMMON_CORPSE_TIMED_DESPAWN, 1800000);
me->SummonCreature(NPC_CAVERNDEEP_AMBUSHER, SpawnPosition[9], TEMPSUMMON_CORPSE_TIMED_DESPAWN, 1800000);
break;
case 2:
if (GameObject* go = me->SummonGameObject(183410, -533.140f, -105.322f, -156.016f, 0.f, QuaternionData(), 1))
{
GoSummonList.push_back(go->GetGUID());
go->SetFlag(GAMEOBJECT_FLAGS, GO_FLAG_NOT_SELECTABLE); //We can't use it!
}
Summon(3);
break;
case 3:
me->SummonCreature(NPC_CAVERNDEEP_AMBUSHER, SpawnPosition[0], TEMPSUMMON_CORPSE_TIMED_DESPAWN, 1800000);
me->SummonCreature(NPC_CAVERNDEEP_AMBUSHER, SpawnPosition[1], TEMPSUMMON_CORPSE_TIMED_DESPAWN, 1800000);
me->SummonCreature(NPC_CAVERNDEEP_AMBUSHER, SpawnPosition[2], TEMPSUMMON_CORPSE_TIMED_DESPAWN, 1800000);
me->SummonCreature(NPC_CAVERNDEEP_AMBUSHER, SpawnPosition[3], TEMPSUMMON_CORPSE_TIMED_DESPAWN, 1800000);
Talk(SAY_BLASTMASTER_7);
break;
case 4:
if (GameObject* go = me->SummonGameObject(183410, -542.199f, -96.854f, -155.790f, 0.f, QuaternionData(), 1))
{
GoSummonList.push_back(go->GetGUID());
go->SetFlag(GAMEOBJECT_FLAGS, GO_FLAG_NOT_SELECTABLE);
}
break;
case 5:
me->SummonCreature(NPC_CAVERNDEEP_AMBUSHER, SpawnPosition[10], TEMPSUMMON_CORPSE_TIMED_DESPAWN, 1800000);
me->SummonCreature(NPC_CAVERNDEEP_AMBUSHER, SpawnPosition[11], TEMPSUMMON_CORPSE_TIMED_DESPAWN, 1800000);
me->SummonCreature(NPC_CAVERNDEEP_AMBUSHER, SpawnPosition[12], TEMPSUMMON_CORPSE_TIMED_DESPAWN, 1800000);
me->SummonCreature(NPC_CAVERNDEEP_AMBUSHER, SpawnPosition[13], TEMPSUMMON_CORPSE_TIMED_DESPAWN, 1800000);
me->SummonCreature(NPC_CAVERNDEEP_AMBUSHER, SpawnPosition[14], TEMPSUMMON_CORPSE_TIMED_DESPAWN, 1800000);
break;
case 6:
if (GameObject* go = me->SummonGameObject(183410, -507.820f, -103.333f, -151.353f, 0.f, QuaternionData(), 1))
{
GoSummonList.push_back(go->GetGUID());
go->SetFlag(GAMEOBJECT_FLAGS, GO_FLAG_NOT_SELECTABLE); //We can't use it!
Summon(5);
}
break;
case 7:
if (GameObject* go = me->SummonGameObject(183410, -511.829f, -86.249f, -151.431f, 0.f, QuaternionData(), 1))
{
GoSummonList.push_back(go->GetGUID());
go->SetFlag(GAMEOBJECT_FLAGS, GO_FLAG_NOT_SELECTABLE); //We can't use it!
}
break;
case 8:
if (Creature* grubbis = me->SummonCreature(NPC_GRUBBIS, SpawnPosition[15], TEMPSUMMON_CORPSE_TIMED_DESPAWN, 1800000))
grubbis->AI()->Talk(SAY_GRUBBIS);
me->SummonCreature(NPC_CHOMPER, SpawnPosition[16], TEMPSUMMON_CORPSE_TIMED_DESPAWN, 1800000);
break;
case 9:
me->SummonGameObject(GO_RED_ROCKET, SpawnPosition[17], QuaternionData(), 7200);
me->SummonGameObject(GO_RED_ROCKET, SpawnPosition[18], QuaternionData(), 7200);
me->SummonGameObject(GO_RED_ROCKET, SpawnPosition[19], QuaternionData(), 7200);
break;
}
}
void UpdateEscortAI(uint32 uiDiff) override
{
if (uiPhase)
{
if (uiTimer <= uiDiff)
{
switch (uiPhase)
{
case 1:
Talk(SAY_BLASTMASTER_1);
NextStep(2000, true);
break;
case 2:
SetEscortPaused(false);
NextStep(0, false, 0);
break;
case 3:
Talk(SAY_BLASTMASTER_2);
SetEscortPaused(false);
NextStep(0, false, 0);
break;
case 4:
Talk(SAY_BLASTMASTER_3);
NextStep(3000, true);
break;
case 5:
Talk(SAY_BLASTMASTER_4);
NextStep(3000, true);
break;
case 6:
SetInFace(true);
Talk(SAY_BLASTMASTER_5);
Summon(1);
if (GameObject* go = ObjectAccessor::GetGameObject(*me, instance->GetGuidData(DATA_GO_CAVE_IN_RIGHT)))
instance->HandleGameObject(ObjectGuid::Empty, true, go);
NextStep(3000, true);
break;
case 7:
Talk(SAY_BLASTMASTER_6);
SetEscortPaused(false);
NextStep(0, false, 0);
break;
case 8:
me->HandleEmoteCommand(EMOTE_STATE_USE_STANDING);
NextStep(25000, true);
break;
case 9:
Summon(2);
NextStep(0, false);
break;
case 10:
Summon(4);
Talk(SAY_BLASTMASTER_8);
NextStep(0, false);
break;
case 11:
Talk(SAY_BLASTMASTER_9);
NextStep(5000, true);
break;
case 12:
Talk(SAY_BLASTMASTER_10);
NextStep(5000, true);
break;
case 13:
Talk(SAY_BLASTMASTER_11);
CaveDestruction(true);
NextStep(8000, true);
break;
case 14:
Talk(SAY_BLASTMASTER_12);
NextStep(8500, true);
break;
case 15:
Talk(SAY_BLASTMASTER_13);
NextStep(2000, true);
break;
case 16:
Talk(SAY_BLASTMASTER_14);
SetInFace(false);
if (GameObject* go = ObjectAccessor::GetGameObject(*me, instance->GetGuidData(DATA_GO_CAVE_IN_LEFT)))
instance->HandleGameObject(ObjectGuid::Empty, true, go);
NextStep(2000, true);
break;
case 17:
SetEscortPaused(false);
Talk(SAY_BLASTMASTER_15);
Summon(5);
NextStep(0, false);
break;
case 18:
Summon(6);
NextStep(0, false);
break;
case 19:
SetInFace(false);
Summon(7);
Talk(SAY_BLASTMASTER_16);
NextStep(0, false);
break;
case 20:
Talk(SAY_BLASTMASTER_18);
NextStep(2000, true);
break;
case 21:
Summon(8);
NextStep(0, false);
break;
case 22:
CaveDestruction(false);
Talk(SAY_BLASTMASTER_11);
NextStep(3000, true);
break;
case 23:
Summon(9);
Talk(SAY_BLASTMASTER_19);
NextStep(0, false);
break;
}
} else uiTimer -= uiDiff;
}
if (!UpdateVictim())
return;
DoMeleeAttackIfReady();
}
void JustSummoned(Creature* summon) override
{
SummonList.push_back(summon->GetGUID());
AggroAllPlayers(summon);
}
};
CreatureAI* GetAI(Creature* creature) const override
{
return GetGnomereganAI<npc_blastmaster_emi_shortfuseAI>(creature);
}
};
class boss_grubbis : public CreatureScript
{
public:
boss_grubbis() : CreatureScript("boss_grubbis") { }
struct boss_grubbisAI : public ScriptedAI
{
boss_grubbisAI(Creature* creature) : ScriptedAI(creature)
{
SetDataSummoner();
}
void SetDataSummoner()
{
if (!me->IsSummon())
return;
if (Unit* summon = me->ToTempSummon()->GetSummoner())
if (Creature* creature = summon->ToCreature())
creature->AI()->SetData(2, 1);
}
void UpdateAI(uint32 /*diff*/) override
{
if (!UpdateVictim())
return;
DoMeleeAttackIfReady();
}
void JustDied(Unit* /*killer*/) override
{
if (!me->IsSummon())
return;
if (Unit* summoner = me->ToTempSummon()->GetSummoner())
if (Creature* creature = summoner->ToCreature())
creature->AI()->SetData(2, 2);
}
};
CreatureAI* GetAI(Creature* creature) const override
{
return GetGnomereganAI<boss_grubbisAI>(creature);
}
};
// 208325 - Extra Parachutes
enum ExtraParachutes
{
ITEM_SAFE_PARACHUTE = 60680
};
class go_gnomeregan_extra_parachutes : public GameObjectScript
{
public:
go_gnomeregan_extra_parachutes() : GameObjectScript("go_gnomeregan_extra_parachutes") { }
bool OnGossipHello(Player* player, GameObject* /*go*/) override
{
// Retail behavior: the crate directly provides one S.A.F.E. Parachute.
// Do not hand out another one while the player already owns one.
if (!player->HasItemCount(ITEM_SAFE_PARACHUTE, 1, true))
player->AddItem(ITEM_SAFE_PARACHUTE, 1);
// The original server-side spell 96364 is incomplete in this branch.
// Handling the click here also prevents the broken default spell cast.
return true;
}
};
// 12709 - Collecting Fallout
class spell_collecting_fallout : public SpellScriptLoader
{
public:
spell_collecting_fallout() : SpellScriptLoader("spell_collecting_fallout") { }
class spell_collecting_fallout_SpellScript : public SpellScript
{
void OnLaunch(SpellEffIndex effIndex)
{
// estimated 25% chance of success
if (roll_chance_i(25))
_spellFail = false;
else
PreventHitDefaultEffect(effIndex);
}
void HandleFail(SpellEffIndex effIndex)
{
if (!_spellFail)
PreventHitDefaultEffect(effIndex);
}
void Register() override
{
OnEffectLaunch.Register(&spell_collecting_fallout_SpellScript::OnLaunch, EFFECT_0, SPELL_EFFECT_TRIGGER_SPELL);
OnEffectLaunch.Register(&spell_collecting_fallout_SpellScript::HandleFail, EFFECT_1, SPELL_EFFECT_TRIGGER_SPELL);
}
bool _spellFail = true;
};
SpellScript* GetSpellScript() const override
{
return new spell_collecting_fallout_SpellScript();
}
};
}
void AddSC_gnomeregan()
{
using namespace Gnomeregan;
new npc_blastmaster_emi_shortfuse();
new boss_grubbis();
new go_gnomeregan_extra_parachutes();
new spell_collecting_fallout();
}