Merge remote-tracking branch 'acore/master' into test-staging

# Conflicts:
#	.github/workflows/windows_build.yml
This commit is contained in:
Keleborn
2026-05-05 13:24:38 -07:00
27 changed files with 441 additions and 99 deletions
+17 -17
View File
@@ -1241,6 +1241,23 @@ void SmartAI::SetFollow(Unit* target, float dist, float angle, uint32 credit, ui
void SmartAI::StopFollow(bool complete)
{
if (complete)
{
Player* player = ObjectAccessor::GetPlayer(*me, mFollowGuid);
if (player && mFollowCredit > 0)
{
if (!mFollowCreditType)
player->RewardPlayerAndGroupAtEvent(mFollowCredit, me);
else
player->GroupEventHappens(mFollowCredit, me);
}
SetDespawnTime(5000);
StartDespawn();
GetScript()->ProcessEventsFor(SMART_EVENT_FOLLOW_COMPLETED, player);
}
mFollowGuid.Clear();
mFollowDist = 0;
mFollowAngle = 0;
@@ -1252,23 +1269,6 @@ void SmartAI::StopFollow(bool complete)
me->GetMotionMaster()->Clear(false);
me->StopMoving();
me->GetMotionMaster()->MoveIdle();
if (!complete)
return;
Player* player = ObjectAccessor::GetPlayer(*me, mFollowGuid);
if (player)
{
if (!mFollowCreditType)
player->RewardPlayerAndGroupAtEvent(mFollowCredit, me);
else
player->GroupEventHappens(mFollowCredit, me);
}
SetDespawnTime(5000);
StartDespawn();
GetScript()->ProcessEventsFor(SMART_EVENT_FOLLOW_COMPLETED, player);
}
void SmartAI::SetScript9(SmartScriptHolder& e, uint32 entry, WorldObject* invoker)
+2
View File
@@ -676,6 +676,8 @@ enum RBACPermissions
RBAC_PERM_COMMAND_CHARACTER_CHECK_PROFESSION = 911,
RBAC_PERM_COMMAND_GOBJECT_LOAD = 912,
RBAC_PERM_COMMAND_BF_QUEUE = 913,
RBAC_PERM_COMMAND_PET_LIST = 914,
RBAC_PERM_COMMAND_PET_DELETE = 915,
// custom permissions 1000+
RBAC_PERM_MAX
};
@@ -2718,7 +2718,8 @@ bool Creature::CanCreatureAttack(Unit const* victim, bool skipDistCheck) const
float x, y, z;
x = y = z = 0.0f;
if (GetMotionMaster()->GetMotionSlot(MOTION_SLOT_IDLE)->GetResetPosition(x, y, z))
MovementGenerator* idleSlot = GetMotionMaster()->GetMotionSlot(MOTION_SLOT_IDLE);
if (idleSlot && idleSlot->GetResetPosition(x, y, z))
return IsInDist2d(x, y, dist);
else
return IsInDist2d(&m_homePosition, dist);
+1 -1
View File
@@ -14351,7 +14351,7 @@ bool Player::CanSeeSpellClickOn(Creature const* c) const
SpellClickInfoMapBounds clickPair = sObjectMgr->GetSpellClickInfoMapBounds(c->GetEntry());
if (clickPair.first == clickPair.second)
return true;
return false;
for (SpellClickInfoContainer::const_iterator itr = clickPair.first; itr != clickPair.second; ++itr)
{
+4 -2
View File
@@ -8387,8 +8387,10 @@ void Unit::EnergizeBySpell(Unit* victim, uint32 spellID, uint32 damage, Powers p
{
victim->ModifyPower(powerType, damage, false);
if (SpellInfo const* spellInfo = sSpellMgr->GetSpellInfo(spellID))
victim->GetThreatMgr().ForwardThreatForAssistingMe(this, float(damage) / 2.0f, spellInfo, true);
// Happiness is internal hunter pet state, not combat assistance — energizing it must not generate threat
if (powerType != POWER_HAPPINESS)
if (SpellInfo const* spellInfo = sSpellMgr->GetSpellInfo(spellID))
victim->GetThreatMgr().ForwardThreatForAssistingMe(this, float(damage) / 2.0f, spellInfo, true);
SendEnergizeSpellLog(victim, spellID, damage, powerType);
}
+28 -7
View File
@@ -459,14 +459,35 @@ void WorldSession::HandleAcceptTradeOpcode(WorldPacket& /*recvPacket*/)
return;
}
// log traded items before moving (pointers become invalid after moveItems)
// cache traded item info before moving (pointers become invalid after moveItems if items merge into existing stacks)
struct TradedItemInfo
{
bool present = false;
std::string name;
uint32 entry = 0;
uint32 count = 0;
};
TradedItemInfo myItemsInfo[TRADE_SLOT_TRADED_COUNT];
TradedItemInfo hisItemsInfo[TRADE_SLOT_TRADED_COUNT];
std::string myItemsStr, hisItemsStr;
for (uint8 i = 0; i < TRADE_SLOT_TRADED_COUNT; ++i)
{
if (myItems[i])
myItemsStr += Acore::StringFormat("{} (Entry:{}) x{}, ", myItems[i]->GetTemplate()->Name1, myItems[i]->GetEntry(), myItems[i]->GetCount());
{
myItemsInfo[i].present = true;
myItemsInfo[i].name = myItems[i]->GetTemplate()->Name1;
myItemsInfo[i].entry = myItems[i]->GetEntry();
myItemsInfo[i].count = myItems[i]->GetCount();
myItemsStr += Acore::StringFormat("{} (Entry:{}) x{}, ", myItemsInfo[i].name, myItemsInfo[i].entry, myItemsInfo[i].count);
}
if (hisItems[i])
hisItemsStr += Acore::StringFormat("{} (Entry:{}) x{}, ", hisItems[i]->GetTemplate()->Name1, hisItems[i]->GetEntry(), hisItems[i]->GetCount());
{
hisItemsInfo[i].present = true;
hisItemsInfo[i].name = hisItems[i]->GetTemplate()->Name1;
hisItemsInfo[i].entry = hisItems[i]->GetEntry();
hisItemsInfo[i].count = hisItems[i]->GetCount();
hisItemsStr += Acore::StringFormat("{} (Entry:{}) x{}, ", hisItemsInfo[i].name, hisItemsInfo[i].entry, hisItemsInfo[i].count);
}
}
// execute trade: 1. remove
@@ -509,10 +530,10 @@ void WorldSession::HandleAcceptTradeOpcode(WorldPacket& /*recvPacket*/)
{
for (uint8 i = 0; i < TRADE_SLOT_TRADED_COUNT; ++i)
{
if (myItems[i])
if (myItemsInfo[i].present)
LOG_GM(GetAccountId(), "GM {} (Account: {}) traded item: {} (Entry: {} Count: {}) to {}",
_player->GetName(), GetAccountId(),
myItems[i]->GetTemplate()->Name1, myItems[i]->GetEntry(), myItems[i]->GetCount(),
myItemsInfo[i].name, myItemsInfo[i].entry, myItemsInfo[i].count,
trader->GetName());
}
if (my_trade->GetMoney() > 0)
@@ -523,10 +544,10 @@ void WorldSession::HandleAcceptTradeOpcode(WorldPacket& /*recvPacket*/)
{
for (uint8 i = 0; i < TRADE_SLOT_TRADED_COUNT; ++i)
{
if (hisItems[i])
if (hisItemsInfo[i].present)
LOG_GM(trader->GetSession()->GetAccountId(), "GM {} (Account: {}) traded item: {} (Entry: {} Count: {}) to {}",
trader->GetName(), trader->GetSession()->GetAccountId(),
hisItems[i]->GetTemplate()->Name1, hisItems[i]->GetEntry(), hisItems[i]->GetCount(),
hisItemsInfo[i].name, hisItemsInfo[i].entry, hisItemsInfo[i].count,
_player->GetName());
}
if (his_trade->GetMoney() > 0)
+10 -1
View File
@@ -1478,6 +1478,15 @@ enum AcoreStrings
LANG_POOL_LOOKUP_IN_POOL = 35431,
LANG_POOL_LOOKUP_NOT_IN_POOL = 35432,
LANG_POOL_LOOKUP_USE_INFO = 35433,
LANG_POOL_LOOKUP_NOTARGET = 35434
LANG_POOL_LOOKUP_NOTARGET = 35434,
// Pet list command
LANG_PET_LIST_HEADER = 35435,
LANG_PET_LIST_ENTRY = 35436,
LANG_PET_LIST_EMPTY = 35437,
// Pet delete command
LANG_PET_DELETE_NOT_FOUND = 35438,
LANG_PET_DELETE_SUCCESS = 35439
};
#endif
@@ -5390,10 +5390,10 @@ void AuraEffect::HandleAuraDummy(AuraApplication const* aurApp, uint8 mode, bool
{
if (target->isDead() && GetBase() && target->IsCreature() && target->GetEntry() == 24601)
{
auto caster2 = GetBase()->GetCaster();
if (caster2 && caster2->IsPlayer())
if (Unit* caster2 = GetBase()->GetCaster())
{
caster2->ToPlayer()->KilledMonsterCredit(25987);
if (Player* player = caster2->GetCharmerOrOwnerPlayerOrPlayerItself())
player->KilledMonsterCredit(25987);
}
}
return;
+104 -3
View File
@@ -17,6 +17,8 @@
#include "Chat.h"
#include "CommandScript.h"
#include "DatabaseEnv.h"
#include <algorithm>
#include "Language.h"
#include "Log.h"
#include "ObjectMgr.h"
@@ -37,9 +39,11 @@ public:
{
static ChatCommandTable petCommandTable =
{
{ "create", HandlePetCreateCommand, rbac::RBAC_PERM_COMMAND_PET_CREATE, Console::No },
{ "learn", HandlePetLearnCommand, rbac::RBAC_PERM_COMMAND_PET_LEARN, Console::No },
{ "unlearn", HandlePetUnlearnCommand, rbac::RBAC_PERM_COMMAND_PET_UNLEARN, Console::No }
{ "create", HandlePetCreateCommand, rbac::RBAC_PERM_COMMAND_PET_CREATE, Console::No },
{ "delete", HandlePetDeleteCommand, rbac::RBAC_PERM_COMMAND_PET_DELETE, Console::Yes },
{ "learn", HandlePetLearnCommand, rbac::RBAC_PERM_COMMAND_PET_LEARN, Console::No },
{ "list", HandlePetListCommand, rbac::RBAC_PERM_COMMAND_PET_LIST, Console::Yes },
{ "unlearn", HandlePetUnlearnCommand, rbac::RBAC_PERM_COMMAND_PET_UNLEARN, Console::No }
};
static ChatCommandTable commandTable =
@@ -126,6 +130,103 @@ public:
return true;
}
static bool HandlePetDeleteCommand(ChatHandler* handler, PlayerIdentifier owner, uint32 petNumber)
{
ObjectGuid ownerGuid = owner.GetGUID();
QueryResult result = CharacterDatabase.Query(
"SELECT entry, name FROM character_pet WHERE id = {} AND owner = {}",
petNumber, ownerGuid.GetCounter());
if (!result)
{
handler->SendErrorMessage(LANG_PET_DELETE_NOT_FOUND, petNumber, owner.GetName(),
ownerGuid.GetCounter());
return false;
}
if (Player* online = owner.GetConnectedPlayer())
{
if (Pet* activePet = online->GetPet())
{
if (activePet->GetCharmInfo() &&
activePet->GetCharmInfo()->GetPetNumber() == petNumber)
{
online->RemovePet(activePet, PET_SAVE_AS_DELETED);
}
}
// Drop the pet from the in-memory PetStable so the stable UI / call-pet
// logic does not reference a row that no longer exists in the database.
if (PetStable* stable = online->GetPetStable())
{
if (stable->CurrentPet && stable->CurrentPet->PetNumber == petNumber)
stable->CurrentPet.reset();
for (Optional<PetStable::PetInfo>& stabled : stable->StabledPets)
if (stabled && stabled->PetNumber == petNumber)
stabled.reset();
stable->UnslottedPets.erase(
std::remove_if(stable->UnslottedPets.begin(), stable->UnslottedPets.end(),
[petNumber](PetStable::PetInfo const& p) { return p.PetNumber == petNumber; }),
stable->UnslottedPets.end());
}
}
Field* fields = result->Fetch();
uint32 entry = fields[0].Get<uint32>();
std::string name = fields[1].Get<std::string>();
std::string creatureName = "<unknown>";
if (CreatureTemplate const* cInfo = sObjectMgr->GetCreatureTemplate(entry))
creatureName = cInfo->Name;
Pet::DeleteFromDB(petNumber);
handler->PSendSysMessage(LANG_PET_DELETE_SUCCESS, petNumber, name, entry, creatureName,
owner.GetName(), ownerGuid.GetCounter());
return true;
}
static bool HandlePetListCommand(ChatHandler* handler, PlayerIdentifier owner)
{
ObjectGuid ownerGuid = owner.GetGUID();
QueryResult result = CharacterDatabase.Query(
"SELECT id, entry, level, slot, name, PetType FROM character_pet "
"WHERE owner = {} ORDER BY slot, id",
ownerGuid.GetCounter());
if (!result)
{
handler->PSendSysMessage(LANG_PET_LIST_EMPTY, owner.GetName(), ownerGuid.GetCounter());
return true;
}
handler->PSendSysMessage(LANG_PET_LIST_HEADER, owner.GetName(), ownerGuid.GetCounter());
do
{
Field* fields = result->Fetch();
uint32 petNumber = fields[0].Get<uint32>();
uint32 entry = fields[1].Get<uint32>();
uint8 level = fields[2].Get<uint8>();
uint8 slot = fields[3].Get<uint8>();
std::string name = fields[4].Get<std::string>();
uint8 petType = fields[5].Get<uint8>();
std::string creatureName = "<unknown>";
if (CreatureTemplate const* cInfo = sObjectMgr->GetCreatureTemplate(entry))
creatureName = cInfo->Name;
handler->PSendSysMessage(LANG_PET_LIST_ENTRY, petNumber, uint32(slot), entry,
creatureName, name, uint32(level), uint32(petType));
} while (result->NextRow());
return true;
}
static bool HandlePetUnlearnCommand(ChatHandler* handler, SpellInfo const* spell)
{
if (!spell)
+20 -6
View File
@@ -189,17 +189,31 @@ public:
return true;
}
static bool HandleSendMoneyCommand(ChatHandler* handler, Optional<PlayerIdentifier> target, QuotedString subject, QuotedString text, uint32 money)
static bool HandleSendMoneyCommand(ChatHandler* handler, Optional<PlayerIdentifier> target, QuotedString subject, QuotedString text, Tail money)
{
if (!target)
{
target = PlayerIdentifier::FromTargetOrSelf(handler);
}
if (!target)
{
return false;
}
auto IsExistWord = [](std::string_view line, std::initializer_list<std::string_view> words)
{
for (auto const& word : words)
{
if (line.find(word) != std::string_view::npos)
return true;
}
return false;
};
Optional<int32> moneyToAddO = IsExistWord(money, { "g", "s", "c" }) ? MoneyStringToMoney(money) : Acore::StringTo<int32>(money);
if (!moneyToAddO)
return false;
int32 moneyToAdd = *moneyToAddO;
// from console show not existed sender
MailSender sender(MAIL_NORMAL, handler->GetSession() ? handler->GetSession()->GetPlayer()->GetGUID().GetCounter() : 0, MAIL_STATIONERY_GM);
@@ -207,7 +221,7 @@ public:
CharacterDatabaseTransaction trans = CharacterDatabase.BeginTransaction();
MailDraft(subject, text)
.AddMoney(money)
.AddMoney(moneyToAdd)
.SendMailTo(trans, MailReceiver(target->GetConnectedPlayer(), target->GetGUID().GetCounter()), sender);
CharacterDatabase.CommitTransaction(trans);
@@ -21,10 +21,10 @@
enum Spells
{
SPELL_SHADOWWORDPAIN = 10894,
SPELL_MANABURN = 10876,
SPELL_PSYCHICSCREAM = 8122,
SPELL_SHADOWSHIELD = 22417
SPELL_SHADOWWORDPAIN = 14032,
SPELL_MANABURN = 14033,
SPELL_PSYCHICSCREAM = 13704,
SPELL_SHADOWSHIELD = 12040
};
struct boss_high_interrogator_gerstahn : public ScriptedAI
@@ -416,6 +416,9 @@ struct boss_sartharion : public BossAI
for (uint32 i : dragons)
if (Creature* boss = instance->GetCreature(i))
boss->DespawnOrUnsummon();
instance->DoRemoveAurasDueToSpellOnPlayers(SPELL_TWILIGHT_TORMENT_VESPERON);
instance->DoRemoveAurasDueToSpellOnPlayers(SPELL_TWILIGHT_TORMENT_SARTHARION);
}
void SetData(uint32 type, uint32 data) override
@@ -838,6 +841,8 @@ struct boss_sartharion_dragonAI : public BossAI
{
Talk(SAY_VESPERON_DEATH);
instance->DoAction(ACTION_CLEAR_PORTAL);
instance->DoRemoveAurasDueToSpellOnPlayers(SPELL_TWILIGHT_TORMENT_VESPERON);
instance->DoRemoveAurasDueToSpellOnPlayers(SPELL_TWILIGHT_TORMENT_SARTHARION);
if (!isCalledBySartharion || instance->GetBossState(DATA_SARTHARION) != IN_PROGRESS)
instance->SetBossState(DATA_VESPERON, DONE);
break;
@@ -215,12 +215,10 @@ public:
instance = me->GetInstanceScript();
if (!instance)
me->IsAIEnabled = false;
first = (instance && !instance->GetPersistentData(PERSISTENT_DATA_INTRO));
}
InstanceScript* instance;
EventMap events;
bool first;
bool shortver;
void Reset() override
@@ -228,9 +226,8 @@ public:
shortver = false;
events.Reset();
me->SetUInt32Value(UNIT_NPC_EMOTESTATE, EMOTE_ONESHOT_NONE);
if (first)
if (instance && !instance->GetPersistentData(PERSISTENT_DATA_INTRO))
{
first = false;
events.ScheduleEvent(EVENT_PRE_INTRO_1, 10s);
events.ScheduleEvent(EVENT_PRE_INTRO_2, 11s);
events.ScheduleEvent(EVENT_EMOTE_LEADER_1, 16s);
@@ -493,51 +493,6 @@ public:
}
};
class npc_icefang : public CreatureScript
{
public:
npc_icefang() : CreatureScript("npc_icefang") { }
struct npc_icefangAI : public npc_escortAI
{
npc_icefangAI(Creature* creature) : npc_escortAI(creature) { }
void AttackStart(Unit* /*who*/) override { }
void JustEngagedWith(Unit* /*who*/) override { }
void EnterEvadeMode(EvadeReason /*why*/) override { }
void PassengerBoarded(Unit* who, int8 /*seatId*/, bool apply) override
{
if (who->IsPlayer())
{
if (apply)
{
me->SetWalk(false);
Start(false, who->GetGUID());
}
}
}
using CreatureAI::WaypointReached;
void WaypointReached(uint32 /*waypointId*/) override { }
void JustDied(Unit* /*killer*/) override { }
void OnCharmed(bool /*apply*/) override { }
void UpdateAI(uint32 diff) override
{
npc_escortAI::UpdateAI(diff);
if (!UpdateVictim())
return;
}
};
CreatureAI* GetAI(Creature* creature) const override
{
return new npc_icefangAI(creature);
}
};
struct npc_hyldsmeet_protodrake : public CreatureAI
{
explicit npc_hyldsmeet_protodrake(Creature* creature) : CreatureAI(creature), _accessoryInstalled(false), _accessoryRespawnTimer(0)
@@ -1344,7 +1299,6 @@ void AddSC_storm_peaks()
RegisterSpellScript(spell_q13007_iron_colossus);
new npc_brunnhildar_prisoner();
new npc_freed_protodrake();
new npc_icefang();
RegisterCreatureAI(npc_hyldsmeet_protodrake);
RegisterSpellScript(spell_close_rift_aura);
new npc_vehicle_d16_propelled_delivery();
+30
View File
@@ -209,6 +209,16 @@ struct npc_pet_dk_ebon_gargoyle : ScriptedAI
_decisionTimer -= diff;
if (!UpdateVictimWithGaze())
{
// Re-engage if we still have a valid victim but lost engagement
// (e.g., PvP combat reference expired during CC like Cyclone)
if (Unit* victim = me->GetVictim())
{
if (me->IsValidAttackTarget(victim))
{
me->EngageWithTarget(victim);
return;
}
}
MySelectNextTarget();
return;
}
@@ -356,6 +366,26 @@ struct npc_pet_dk_army_of_the_dead : public AggressorAI
if (me->IsValidAttackTarget(attacker))
AttackStart(attacker);
}
void UpdateAI(uint32 /*diff*/) override
{
if (!UpdateVictim())
{
// Re-engage if we still have a valid victim but lost engagement
// (e.g., combat reference expired during CC like knockback)
if (Unit* victim = me->GetVictim())
{
if (me->IsValidAttackTarget(victim))
{
me->EngageWithTarget(victim);
return;
}
}
return;
}
DoMeleeAttackIfReady();
}
};
struct npc_pet_dk_dancing_rune_weapon : public NullCreatureAI