Merge pull request #1466 from Wishmaster117/Add-glyphs-and-glyph-equip-commands

Add /w botname "glyphs" and  "glyph equip" commands
This commit is contained in:
kadeshar
2025-07-27 12:30:54 +02:00
committed by GitHub
11 changed files with 366 additions and 0 deletions

View File

@@ -11,9 +11,18 @@
#include "PlayerbotAIConfig.h"
#include "PlayerbotFactory.h"
#include "Playerbots.h"
#include "AiObjectContext.h"
#include "Log.h"
bool ChangeTalentsAction::Execute(Event event)
{
auto* flag = botAI->GetAiObjectContext()->GetValue<bool>("custom_glyphs"); // Added for custom Glyphs
if (flag->Get()) // Added for custom Glyphs
{
flag->Set(false);
LOG_INFO("playerbots", "Custom Glyph Flag set to OFF");
}
std::string param = event.getParam();
std::ostringstream out;

View File

@@ -79,6 +79,8 @@
#include "UnlockItemAction.h"
#include "UnlockTradedItemAction.h"
#include "PetAction.h"
#include "TellGlyphsAction.h"
#include "EquipGlyphsAction.h"
class ChatActionContext : public NamedObjectContext<Action>
{
@@ -189,6 +191,8 @@ public:
creators["calc"] = &ChatActionContext::calc;
creators["wipe"] = &ChatActionContext::wipe;
creators["pet"] = &ChatActionContext::pet;
creators["glyphs"] = &ChatActionContext::glyphs; // Added for custom Glyphs
creators["glyph equip"] = &ChatActionContext::glyph_equip; // Added for custom Glyphs
}
private:
@@ -296,6 +300,8 @@ private:
static Action* calc(PlayerbotAI* ai) { return new TellCalculateItemAction(ai); }
static Action* wipe(PlayerbotAI* ai) { return new WipeAction(ai); }
static Action* pet(PlayerbotAI* botAI) { return new PetAction(botAI); }
static Action* glyphs(PlayerbotAI* botAI) { return new TellGlyphsAction(botAI); } // Added for custom Glyphs
static Action* glyph_equip(PlayerbotAI* ai) { return new EquipGlyphsAction(ai); } // Added for custom Glyphs
};
#endif

View File

@@ -0,0 +1,159 @@
/*
* Copyright (C) 2016+ AzerothCore <www.azerothcore.org>, released under GNU GPL v2 license, you may redistribute it
* and/or modify it under version 2 of the License, or (at your option), any later version.
*/
#include "EquipGlyphsAction.h"
#include "Playerbots.h"
#include "ObjectMgr.h"
#include "SpellMgr.h"
#include "DBCStores.h"
#include "AiObjectContext.h"
#include "Log.h"
#include <unordered_map>
#include <sstream>
#include <unordered_set>
namespace
{
// itemId -> GlyphInfo
std::unordered_map<uint32, EquipGlyphsAction::GlyphInfo> s_GlyphCache;
}
void EquipGlyphsAction::BuildGlyphCache()
{
if (!s_GlyphCache.empty())
return;
ItemTemplateContainer const* store = sObjectMgr->GetItemTemplateStore();
for (auto const& kv : *store)
{
uint32 itemId = kv.first;
ItemTemplate const* proto = &kv.second;
if (!proto || proto->Class != ITEM_CLASS_GLYPH)
continue;
// inspect item spell
for (uint32 i = 0; i < MAX_ITEM_PROTO_SPELLS; ++i)
{
uint32 spellId = proto->Spells[i].SpellId;
if (!spellId) continue;
SpellInfo const* si = sSpellMgr->GetSpellInfo(spellId);
if (!si) continue;
for (uint8 eff = 0; eff <= EFFECT_2; ++eff)
{
if (si->Effects[eff].Effect != SPELL_EFFECT_APPLY_GLYPH)
continue;
uint32 glyphId = si->Effects[eff].MiscValue;
if (!glyphId) continue;
if (auto const* gp = sGlyphPropertiesStore.LookupEntry(glyphId))
s_GlyphCache[itemId] = {gp, proto};
}
}
}
}
EquipGlyphsAction::GlyphInfo const* EquipGlyphsAction::GetGlyphInfo(uint32 itemId)
{
BuildGlyphCache();
auto it = s_GlyphCache.find(itemId);
return (it == s_GlyphCache.end()) ? nullptr : &it->second;
}
/// -----------------------------------------------------------------
/// Validation and collect
/// -----------------------------------------------------------------
bool EquipGlyphsAction::CollectGlyphs(std::vector<uint32> const& itemIds,
std::vector<GlyphInfo const*>& out) const
{
std::unordered_set<uint32> seen;
for (uint32 itemId : itemIds)
{
if (!seen.insert(itemId).second)
return false; // double
auto const* info = GetGlyphInfo(itemId);
if (!info) // no good glyph
return false;
// check class by AllowableClass
if ((info->proto->AllowableClass & bot->getClassMask()) == 0)
return false;
out.push_back(info);
}
return out.size() <= 6 && !out.empty();
}
/// -----------------------------------------------------------------
/// Action
/// -----------------------------------------------------------------
bool EquipGlyphsAction::Execute(Event event)
{
// 1) parse IDs
std::vector<uint32> itemIds;
std::istringstream iss(event.getParam());
for (uint32 id; iss >> id; ) itemIds.push_back(id);
std::vector<GlyphInfo const*> glyphs;
if (!CollectGlyphs(itemIds, glyphs))
{
botAI->TellMaster("Usage: glyph equip <6 glyph item IDs> (3 major, 3 minor).");
return false;
}
// 2) prepare a empty slots table ?
bool used[6] = {false,false,false,false,false,false};
// 3) for each glyph, find the first available and compatible socket
for (auto const* g : glyphs)
{
bool placed = false;
for (uint8 i = 0; i < MAX_GLYPH_SLOT_INDEX; ++i)
{
if (used[i]) continue;
uint32 slotId = bot->GetGlyphSlot(i);
auto const* gs = sGlyphSlotStore.LookupEntry(slotId);
if (!gs || gs->TypeFlags != g->prop->TypeFlags)
continue; // major/minor don't match
// Remove aura if exist
uint32 cur = bot->GetGlyph(i);
if (cur)
if (auto* old = sGlyphPropertiesStore.LookupEntry(cur))
bot->RemoveAurasDueToSpell(old->SpellId);
// Apply new one
bot->CastSpell(bot, g->prop->SpellId, true);
bot->SetGlyph(i, g->prop->Id, true);
used[i] = true;
placed = true;
break;
}
if (!placed)
{
botAI->TellMaster("Not enought empty sockets for all glyphs.");
return false;
}
}
botAI->TellMaster("Glyphs updated.");
// Flag for custom glyphs
botAI->GetAiObjectContext()->GetValue<bool>("custom_glyphs")->Set(true);
LOG_INFO("playerbots", "Custom Glyph Flag set to ON");
return true;
}

View File

@@ -0,0 +1,37 @@
/*
* Copyright (C) 2016+ AzerothCore <www.azerothcore.org>, released under GNU GPL v2 license, you may redistribute it
* and/or modify it under version 2 of the License, or (at your option), any later version.
*/
#ifndef _PLAYERBOT_EQUIPGLYPHSACTION_H
#define _PLAYERBOT_EQUIPGLYPHSACTION_H
#include "Action.h"
// 1 = major, 2 = minor dans GlyphProperties.dbc
enum class GlyphKind : uint32 { MAJOR = 1, MINOR = 2 };
class EquipGlyphsAction : public Action
{
public:
EquipGlyphsAction(PlayerbotAI* ai) : Action(ai, "glyph equip") {}
bool Execute(Event event) override;
/// ---- Rendu public pour être utilisable par le cache global ----
struct GlyphInfo
{
GlyphPropertiesEntry const* prop; ///< entrée GlyphProperties.dbc
ItemTemplate const* proto; ///< template de lobjet glyphe
};
private:
/// Construit la cache {itemId -> GlyphInfo}
static void BuildGlyphCache();
static GlyphInfo const* GetGlyphInfo(uint32 itemId);
/// Parse & valide la liste ditems glyphes
bool CollectGlyphs(std::vector<uint32> const& itemIds,
std::vector<GlyphInfo const*>& out) const;
};
#endif

View File

@@ -0,0 +1,113 @@
/*
* Copyright (C) 2016+ AzerothCore <www.azerothcore.org>, released under GNU GPL v2 license, you may redistribute it
* and/or modify it under version 2 of the License, or (at your option), any later version.
*/
#include "TellGlyphsAction.h"
#include "Event.h"
#include "Playerbots.h"
#include "ObjectMgr.h"
#include "SpellMgr.h"
#include "World.h"
#include <unordered_map>
#include <sstream>
namespace
{
// -----------------------------------------------------------------
// Cache : GlyphID (MiscValue) -> ItemTemplate*
// -----------------------------------------------------------------
std::unordered_map<uint32, ItemTemplate const*> s_GlyphItemCache;
void BuildGlyphItemCache()
{
if (!s_GlyphItemCache.empty())
return;
ItemTemplateContainer const* store = sObjectMgr->GetItemTemplateStore();
for (auto const& kv : *store) // C++17 : range-for sur map
{
ItemTemplate const* proto = &kv.second;
if (!proto || proto->Class != ITEM_CLASS_GLYPH)
continue;
for (uint32 i = 0; i < MAX_ITEM_PROTO_SPELLS; ++i)
{
uint32 spellId = proto->Spells[i].SpellId;
if (!spellId)
continue;
SpellInfo const* spell = sSpellMgr->GetSpellInfo(spellId);
if (!spell)
continue;
for (uint32 eff = 0; eff <= EFFECT_2; ++eff)
{
if (spell->Effects[eff].Effect != SPELL_EFFECT_APPLY_GLYPH)
continue;
uint32 glyphId = spell->Effects[eff].MiscValue;
if (glyphId)
s_GlyphItemCache[glyphId] = proto;
}
}
}
}
} // namespace
// -----------------------------------------------------------------
// Action
// -----------------------------------------------------------------
bool TellGlyphsAction::Execute(Event event)
{
//-----------------------------------------------------------------
// 1. who sended the wisp ? (source of event)
//-----------------------------------------------------------------
Player* sender = event.getOwner(); // API Event
if (!sender)
return false;
//-----------------------------------------------------------------
// 2. Generate glyphId cache -> item
//-----------------------------------------------------------------
BuildGlyphItemCache();
//-----------------------------------------------------------------
// 3. Look at the 6 glyphs sockets
//-----------------------------------------------------------------
std::ostringstream list;
bool first = true;
for (uint8 slot = 0; slot < MAX_GLYPH_SLOT_INDEX; ++slot)
{
uint32 glyphId = bot->GetGlyph(slot);
if (!glyphId)
continue;
auto it = s_GlyphItemCache.find(glyphId);
if (it == s_GlyphItemCache.end())
continue; // No glyph found (rare)
if (!first)
list << ", ";
// chat->FormatItem
list << chat->FormatItem(it->second);
first = false;
}
//-----------------------------------------------------------------
// 4. Send chat messages
//-----------------------------------------------------------------
if (first) // no glyphs
botAI->TellMaster("No glyphs equipped");
else
botAI->TellMaster(std::string("Glyphs: ") + list.str());
return true;
}

View File

@@ -0,0 +1,21 @@
/*
* Copyright (C) 2016+ AzerothCore <www.azerothcore.org>, released under GNU GPL v2 license, you may redistribute it
* and/or modify it under version 2 of the License, or (at your option), any later version.
*/
#ifndef _PLAYERBOT_TELLGLYPHSACTION_H
#define _PLAYERBOT_TELLGLYPHSACTION_H
#include "Action.h"
class TellGlyphsAction : public Action
{
public:
TellGlyphsAction(PlayerbotAI* ai, std::string const name = "glyphs")
: Action(ai, name) {}
bool Execute(Event event) override;
};
#endif