Paragon tester hunter BiS, mount cast QoL, learn all mounts RBAC, trade cap 11
- mod-paragon: .paragon tester bis hunter (Sanctified Ahn'Kahar Blood Hunter + Windrunner's Heartseeker), bis gems kits, AGI bow vs ranged/gun/crossbow, ranged for spi/hybrid weapons. - .learn all mounts: RBAC 916 + db_auth migration 2026_05_12_00.sql. - Cast-time mount spells: allow start/complete while moving; block in combat; interrupt mount cast on combat enter; relax movement prevention for NPCs/units. - MaxPrimaryTradeSkill default 11 (all WotLK primary professions) in WorldConfig + worldserver.conf.dist. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -0,0 +1,10 @@
|
||||
-- DB update 2026_05_03_00 -> 2026_05_12_00
|
||||
-- RBAC permission for .learn all mounts (Admin 196, Gamemaster 197).
|
||||
DELETE FROM `rbac_permissions` WHERE `id` = 916;
|
||||
INSERT INTO `rbac_permissions` (`id`, `name`) VALUES
|
||||
(916, 'Command: learn all mounts');
|
||||
|
||||
DELETE FROM `rbac_linked_permissions` WHERE `linkedId` = 916;
|
||||
INSERT INTO `rbac_linked_permissions` (`id`, `linkedId`) VALUES
|
||||
(196, 916),
|
||||
(197, 916);
|
||||
@@ -8,9 +8,11 @@
|
||||
* separate (see README).
|
||||
*/
|
||||
|
||||
#include "Bag.h"
|
||||
#include "CharacterDatabase.h"
|
||||
#include "Chat.h"
|
||||
#include "CommandScript.h"
|
||||
#include "Language.h"
|
||||
#include "Config.h"
|
||||
#include "Pet.h"
|
||||
#include "Player.h"
|
||||
@@ -23,6 +25,7 @@
|
||||
#include "SpellMgr.h"
|
||||
#include "WorldDatabase.h"
|
||||
#include "WorldPacket.h"
|
||||
#include "ObjectMgr.h"
|
||||
#include "Log.h"
|
||||
#include "DBCEnums.h"
|
||||
#include "DBCStores.h"
|
||||
@@ -32,6 +35,7 @@
|
||||
#include <array>
|
||||
#include <cctype>
|
||||
#include <cstdlib>
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
#include <unordered_map>
|
||||
#include <unordered_set>
|
||||
@@ -4592,6 +4596,487 @@ public:
|
||||
}
|
||||
};
|
||||
|
||||
// --- Paragon tester inventory helpers (ICC 25H-style curated lists; edit ids here for your fork) ---
|
||||
// Paragon can equip any armor weight; BiS picks below intentionally mix plate/mail/leather/cloth per slot.
|
||||
|
||||
uint32 ParagonTesterSelectLargestUsableBagItemId(Player const* player)
|
||||
{
|
||||
static constexpr uint32 candidates[] = { 51809, 41600, 41599, 38082 };
|
||||
for (uint32 id : candidates)
|
||||
{
|
||||
ItemTemplate const* proto = sObjectMgr->GetItemTemplate(id);
|
||||
if (!proto || proto->Class != ITEM_CLASS_CONTAINER || proto->InventoryType != INVTYPE_BAG)
|
||||
continue;
|
||||
if (player->CanUseItem(proto) == EQUIP_ERR_OK)
|
||||
return id;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
uint32 ParagonTesterGrantItemList(Player* target, uint32 const* ids, size_t count, ChatHandler* handler)
|
||||
{
|
||||
uint32 granted = 0;
|
||||
for (size_t i = 0; i < count; ++i)
|
||||
{
|
||||
uint32 const itemId = ids[i];
|
||||
if (!itemId)
|
||||
continue;
|
||||
|
||||
ItemTemplate const* proto = sObjectMgr->GetItemTemplate(itemId);
|
||||
if (!proto)
|
||||
{
|
||||
handler->PSendSysMessage("Paragon tester kit: item {} is not defined (skipped).", itemId);
|
||||
continue;
|
||||
}
|
||||
|
||||
ItemPosCountVec dest;
|
||||
InventoryResult const msg = target->CanStoreNewItem(NULL_BAG, NULL_SLOT, dest, itemId, 1);
|
||||
if (msg != EQUIP_ERR_OK || dest.empty())
|
||||
{
|
||||
handler->PSendSysMessage("Paragon tester kit: cannot store {} ({}) — free bag space?", itemId, uint32(msg));
|
||||
continue;
|
||||
}
|
||||
|
||||
if (Item* item = target->StoreNewItem(dest, itemId, true))
|
||||
{
|
||||
item->SetBinding(false);
|
||||
++granted;
|
||||
}
|
||||
}
|
||||
return granted;
|
||||
}
|
||||
|
||||
struct ParagonTesterStackedGrant
|
||||
{
|
||||
uint32 itemId;
|
||||
uint32 count;
|
||||
};
|
||||
|
||||
// Grants stackable/consumable items in one StoreNewItem per line (gems, scrolls, scopes, etc.).
|
||||
uint32 ParagonTesterGrantStackedItemList(Player* target, ParagonTesterStackedGrant const* grants, size_t grantCount, ChatHandler* handler)
|
||||
{
|
||||
uint32 totalPieces = 0;
|
||||
for (size_t i = 0; i < grantCount; ++i)
|
||||
{
|
||||
uint32 const itemId = grants[i].itemId;
|
||||
uint32 count = grants[i].count;
|
||||
if (!itemId || !count)
|
||||
continue;
|
||||
|
||||
ItemTemplate const* proto = sObjectMgr->GetItemTemplate(itemId);
|
||||
if (!proto)
|
||||
{
|
||||
handler->PSendSysMessage("Paragon tester kit: item {} is not defined (skipped).", itemId);
|
||||
continue;
|
||||
}
|
||||
|
||||
uint32 noSpaceForCount = 0;
|
||||
ItemPosCountVec dest;
|
||||
InventoryResult const msg = target->CanStoreNewItem(NULL_BAG, NULL_SLOT, dest, itemId, count, &noSpaceForCount);
|
||||
if (msg != EQUIP_ERR_OK)
|
||||
count -= noSpaceForCount;
|
||||
|
||||
if (!count || dest.empty())
|
||||
{
|
||||
handler->PSendSysMessage("Paragon tester kit: cannot store {} x{} ({}) — free bag space?", itemId, grants[i].count, uint32(msg));
|
||||
continue;
|
||||
}
|
||||
|
||||
if (Item* item = target->StoreNewItem(dest, itemId, true))
|
||||
{
|
||||
item->SetBinding(false);
|
||||
totalPieces += count;
|
||||
}
|
||||
}
|
||||
return totalPieces;
|
||||
}
|
||||
|
||||
void ParagonTesterClearNonEquipmentInventory(Player* player)
|
||||
{
|
||||
for (uint8 bagSlot = INVENTORY_SLOT_BAG_START; bagSlot < INVENTORY_SLOT_BAG_END; ++bagSlot)
|
||||
{
|
||||
if (Bag* bag = player->GetBagByPos(bagSlot))
|
||||
{
|
||||
for (uint32 j = 0; j < bag->GetBagSize(); ++j)
|
||||
{
|
||||
if (bag->GetItemByPos(j))
|
||||
player->DestroyItem(bagSlot, j, true);
|
||||
}
|
||||
}
|
||||
|
||||
if (player->GetItemByPos(INVENTORY_SLOT_BAG_0, bagSlot))
|
||||
player->DestroyItem(INVENTORY_SLOT_BAG_0, bagSlot, true);
|
||||
}
|
||||
|
||||
for (uint8 slot = INVENTORY_SLOT_ITEM_START; slot < INVENTORY_SLOT_ITEM_END; ++slot)
|
||||
{
|
||||
if (player->GetItemByPos(INVENTORY_SLOT_BAG_0, slot))
|
||||
player->DestroyItem(INVENTORY_SLOT_BAG_0, slot, true);
|
||||
}
|
||||
}
|
||||
|
||||
void ParagonTesterStringToLowerAscii(std::string& s)
|
||||
{
|
||||
for (char& c : s)
|
||||
c = static_cast<char>(std::tolower(static_cast<unsigned char>(c)));
|
||||
}
|
||||
|
||||
std::string ParagonTesterNormalizeWeaponTypeKey(std::string_view raw)
|
||||
{
|
||||
std::string t;
|
||||
for (unsigned char ch : raw)
|
||||
{
|
||||
if (ch == ' ' || ch == '\t' || ch == '-' || ch == '_' || ch == '/')
|
||||
continue;
|
||||
t.push_back(static_cast<char>(std::tolower(ch)));
|
||||
}
|
||||
return t;
|
||||
}
|
||||
|
||||
// If any bag slot 19–22 is free and the item is a container, move it from inventory onto that slot (Player.cpp pattern).
|
||||
bool ParagonTesterTryEquipBagToFirstEmptySlot(Player* player, Item* bag)
|
||||
{
|
||||
if (!player || !bag)
|
||||
return false;
|
||||
|
||||
ItemTemplate const* proto = bag->GetTemplate();
|
||||
if (!proto || proto->Class != ITEM_CLASS_CONTAINER || proto->InventoryType != INVTYPE_BAG)
|
||||
return false;
|
||||
|
||||
uint16 eDest = 0;
|
||||
if (player->CanEquipItem(NULL_SLOT, eDest, bag, false) != EQUIP_ERR_OK)
|
||||
return false;
|
||||
|
||||
uint8 const srcBag = bag->GetBagSlot();
|
||||
uint8 const srcSlot = bag->GetSlot();
|
||||
player->RemoveItem(srcBag, srcSlot, true);
|
||||
player->EquipItem(eDest, bag, true);
|
||||
return true;
|
||||
}
|
||||
|
||||
// Curated ICC-era ids (db_world item_template). Extend as needed for your fork.
|
||||
bool ParagonTesterResolveWeaponKit(std::string statRaw, std::string typeRaw, std::vector<uint32>& out, std::string& err)
|
||||
{
|
||||
out.clear();
|
||||
ParagonTesterStringToLowerAscii(statRaw);
|
||||
// trim stat
|
||||
while (!statRaw.empty() && statRaw.front() == ' ')
|
||||
statRaw.erase(statRaw.begin());
|
||||
while (!statRaw.empty() && statRaw.back() == ' ')
|
||||
statRaw.pop_back();
|
||||
|
||||
std::string stat = statRaw;
|
||||
if (stat == "strength")
|
||||
stat = "str";
|
||||
else if (stat == "agility" || stat == "dex" || stat == "dexterity")
|
||||
stat = "agi";
|
||||
else if (stat == "intellect")
|
||||
stat = "int";
|
||||
else if (stat == "spirit")
|
||||
stat = "spi";
|
||||
else if (stat == "apsp" || stat == "spellstrike")
|
||||
stat = "hybrid";
|
||||
|
||||
std::string const wkey = ParagonTesterNormalizeWeaponTypeKey(typeRaw);
|
||||
if (stat.empty() || wkey.empty())
|
||||
{
|
||||
err = "usage: .paragon tester weapons <stat> <type> — see `.paragon tester weapons` with no args for help.";
|
||||
return false;
|
||||
}
|
||||
|
||||
auto push = [&](std::initializer_list<uint32> ids)
|
||||
{
|
||||
for (uint32 id : ids)
|
||||
if (id)
|
||||
out.push_back(id);
|
||||
};
|
||||
|
||||
if (stat == "str")
|
||||
{
|
||||
// "2h sword", "2h/sword", "2h axe" → 2hsword / 2haxe after normalize (slashes stripped like spaces).
|
||||
if (wkey == "2hsword" || wkey == "twohandsword")
|
||||
push({ 50730 }); // Glorenzelg (2H sword)
|
||||
else if (wkey == "2haxe" || wkey == "twohandaxe")
|
||||
push({ 50709 }); // Bryntroll (2H axe)
|
||||
else if (wkey == "2hmace" || wkey == "twohandmace")
|
||||
push({ 50603 }); // Cryptmaker (2H mace)
|
||||
else if (wkey == "1hsword" || wkey == "onehandsword")
|
||||
push({ 50737 }); // Havoc's Call (1H sword)
|
||||
else if (wkey == "1haxe" || wkey == "onehandaxe")
|
||||
push({ 50654 }); // Scourgeborne Waraxe (1H axe)
|
||||
else if (wkey == "1hmace" || wkey == "onehandmace" || wkey == "1hhammer")
|
||||
push({ 50738 }); // Mithrios (1H mace)
|
||||
else if (wkey == "2h" || wkey == "twohand" || wkey == "zwei" || wkey == "great" || wkey == "polearm")
|
||||
push({ 50730 });
|
||||
else if (wkey == "dual" || wkey == "dw" || wkey == "dualwield" || wkey == "dualwielding")
|
||||
push({ 50738, 50737 });
|
||||
else if (wkey == "sword" || wkey == "swords" || wkey == "1h")
|
||||
push({ 50737 });
|
||||
else if (wkey == "mace" || wkey == "hammer")
|
||||
push({ 50738 });
|
||||
else if (wkey == "axe")
|
||||
push({ 50654 });
|
||||
else if (wkey == "ranged" || wkey == "bow" || wkey == "gun" || wkey == "crossbow" || wkey == "thrown")
|
||||
push({ 50733 });
|
||||
else
|
||||
{
|
||||
err = fmt::format(
|
||||
"unknown STR weapon type \"{}\" (try 2h sword, 2h axe, 2h mace, 1h sword, 1h axe, 1h mace, 2h, dual, sword, mace, axe, ranged).",
|
||||
typeRaw);
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
if (stat == "agi")
|
||||
{
|
||||
if (wkey == "2h" || wkey == "twohand" || wkey == "polearm")
|
||||
push({ 50735 });
|
||||
else if (wkey == "dual" || wkey == "dw" || wkey == "dualwield" || wkey == "daggers")
|
||||
push({ 50736, 50676 });
|
||||
else if (wkey == "dagger")
|
||||
push({ 50736 });
|
||||
else if (wkey == "sword" || wkey == "swords" || wkey == "1h")
|
||||
push({ 50672 });
|
||||
else if (wkey == "fist" || wkey == "fistweapon" || wkey == "claw")
|
||||
push({ 50676 });
|
||||
else if (wkey == "staff" || wkey == "staves")
|
||||
push({ 50731 }); // caster staff; use as generic high-ilvl staff for testers
|
||||
else if (wkey == "bow")
|
||||
push({ 51940 }); // Windrunner's Heartseeker (hunter-style bow)
|
||||
else if (wkey == "ranged" || wkey == "gun" || wkey == "crossbow" || wkey == "thrown")
|
||||
push({ 50733 }); // Fal'inrush (BiS gun)
|
||||
else
|
||||
{
|
||||
err = fmt::format("unknown AGI weapon type \"{}\" (try 2h, dual, dagger, sword, fist, staff, bow, ranged).", typeRaw);
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
if (stat == "int")
|
||||
{
|
||||
if (wkey == "staff" || wkey == "staves")
|
||||
push({ 50731 });
|
||||
else if (wkey == "wand")
|
||||
push({ 50684 });
|
||||
else if (wkey == "mhoh" || wkey == "ohmh" || wkey == "dual" || wkey == "dw" || wkey == "moh")
|
||||
push({ 50732, 50734 });
|
||||
else if (wkey == "mh" || wkey == "mainhand" || wkey == "sword" || wkey == "mace" || wkey == "dagger")
|
||||
push({ 50732 });
|
||||
else if (wkey == "oh" || wkey == "offhand")
|
||||
push({ 50734 });
|
||||
else if (wkey == "shield")
|
||||
push({ 50729 });
|
||||
else if (wkey == "ranged" || wkey == "bow" || wkey == "gun" || wkey == "crossbow")
|
||||
push({ 50733 });
|
||||
else
|
||||
{
|
||||
err = fmt::format("unknown INT weapon type \"{}\" (try staff, wand, mhoh, mh, oh, shield, ranged).", typeRaw);
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
if (stat == "spi")
|
||||
{
|
||||
if (wkey == "staff" || wkey == "staves")
|
||||
push({ 50725 });
|
||||
else if (wkey == "wand")
|
||||
push({ 50684 });
|
||||
else if (wkey == "mace" || wkey == "mh")
|
||||
push({ 50732 });
|
||||
else if (wkey == "mhoh" || wkey == "ohmh")
|
||||
push({ 50732, 50734 });
|
||||
else if (wkey == "shield")
|
||||
push({ 50729 });
|
||||
else if (wkey == "ranged" || wkey == "bow" || wkey == "gun" || wkey == "crossbow" || wkey == "thrown")
|
||||
push({ 50733 });
|
||||
else
|
||||
{
|
||||
err = fmt::format("unknown SPI weapon type \"{}\" (try staff, wand, mace, mhoh, shield, ranged).", typeRaw);
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
if (stat == "tank")
|
||||
{
|
||||
if (wkey == "shield")
|
||||
push({ 50729 });
|
||||
else if (wkey == "sword" || wkey == "swords" || wkey == "1h")
|
||||
push({ 50738 });
|
||||
else if (wkey == "mace" || wkey == "hammer")
|
||||
push({ 50738 });
|
||||
else if (wkey == "swordboard" || wkey == "sb" || wkey == "mit" || wkey == "1hshield" || wkey == "threat")
|
||||
push({ 50738, 50729 });
|
||||
else if (wkey == "dual" || wkey == "dw")
|
||||
push({ 50738, 50737 });
|
||||
else if (wkey == "2h" || wkey == "twohand")
|
||||
push({ 50730 });
|
||||
else if (wkey == "ranged" || wkey == "bow" || wkey == "gun" || wkey == "crossbow" || wkey == "thrown")
|
||||
push({ 50733 });
|
||||
else if (wkey == "sigil" || wkey == "relic")
|
||||
push({ 50462 });
|
||||
else
|
||||
{
|
||||
err = fmt::format("unknown tank weapon type \"{}\" (try swordboard, shield, sword, mace, dual, 2h, ranged, sigil).", typeRaw);
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
if (stat == "hybrid")
|
||||
{
|
||||
if (wkey == "staff")
|
||||
push({ 50731 });
|
||||
else if (wkey == "wand")
|
||||
push({ 50684 });
|
||||
else if (wkey == "shield")
|
||||
push({ 50729 });
|
||||
else if (wkey == "2h" || wkey == "twohand")
|
||||
push({ 50735 });
|
||||
else if (wkey == "dual" || wkey == "dw" || wkey == "mhoh" || wkey == "default")
|
||||
push({ 50732, 50734 });
|
||||
else if (wkey == "ranged" || wkey == "bow" || wkey == "gun" || wkey == "crossbow" || wkey == "thrown")
|
||||
push({ 50733 });
|
||||
else
|
||||
{
|
||||
err = fmt::format("unknown hybrid weapon type \"{}\" (try mhoh, staff, wand, shield, 2h, ranged).", typeRaw);
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
err = fmt::format("unknown stat \"{}\" (use str, agi, int, spi, tank, hybrid).", statRaw);
|
||||
return false;
|
||||
}
|
||||
|
||||
// Order: head, shoulders, chest, hands, legs, bracers, belt, boots, neck, cloak, weapons…, trinkets, rings.
|
||||
// Item ids verified against stock AC db_world item_template (3.3.5a).
|
||||
static constexpr uint32 kTesterBisStr[] = {
|
||||
50712, 51229, 50656, 50675, 50624, // helm, shoulders (51229), chest, hands, legs — not 51211 (that id is Ymirjar legs)
|
||||
54580, 50620, 54578, 54581, 50653, // Umbrage + Coldwraith + Apocalypse's Advance + Penumbra + Shadowvault
|
||||
50730, 50733,
|
||||
50363, 54590,
|
||||
50657, 54576,
|
||||
};
|
||||
|
||||
static constexpr uint32 kTesterBisAgi[] = {
|
||||
51242, 51299, 51298, 51243, 51241, // Frost Witch (mail) + Lasherweave (leather) mix
|
||||
50670, 50688, 50607, 50633, 50653,
|
||||
50736, 50733,
|
||||
50363, 54590,
|
||||
50657, 54576,
|
||||
};
|
||||
|
||||
static constexpr uint32 kTesterBisInt[] = {
|
||||
51281, 51245, 51283, 51280, 51246, // Bloodmage cloth + Frost Witch (mail) shoulders/legs
|
||||
50686, 50702, 50699, 50724, 50628,
|
||||
50732, 50734, 50684,
|
||||
50346, 50360,
|
||||
50610, 50664,
|
||||
};
|
||||
|
||||
static constexpr uint32 kTesterBisSpi[] = {
|
||||
51237, 51257, 51239, 51256, 51258, // Resto Frost Witch (mail) + Crimson Acolyte (cloth)
|
||||
50686, 50702, 50699, 50724, 50628,
|
||||
50725,
|
||||
50360, 50366,
|
||||
50610, 50664,
|
||||
};
|
||||
|
||||
static constexpr uint32 kTesterBisTank[] = {
|
||||
51306, 51309, 51305, 51307, 51308, // Sanctified Scourgelord (plate, DK tank profile)
|
||||
50611, 50620, 50625, 50609, 50677,
|
||||
50738, 50729, 50462,
|
||||
50364, 54591,
|
||||
50404, 50657,
|
||||
};
|
||||
|
||||
// AP main-hand + SP off-hand + mail enhancer T10 (ICC); for hybrid battlemage-style testers.
|
||||
static constexpr uint32 kTesterBisHybrid[] = {
|
||||
51242, 51240, 51244, 51243, 51241,
|
||||
54580, 50620, 54578, 54581, 50653,
|
||||
50732, 50734,
|
||||
50363, 50346,
|
||||
50657, 50610,
|
||||
};
|
||||
|
||||
// Sanctified Ahn'Kahar Blood Hunter (277) + ICC phys offsets; ranged slot only (Windrunner's Heartseeker).
|
||||
static constexpr uint32 kTesterBisHunter[] = {
|
||||
51286, 51288, 51289, 51285, 51287,
|
||||
50670, 50688, 50607, 50633, 50653,
|
||||
0, 51940,
|
||||
50363, 54590,
|
||||
50657, 54576,
|
||||
};
|
||||
|
||||
// ICC-era gems (stacked), enchant scrolls, belt buckle, leg armor/spellthread, Sons of Hodir shoulders, Ebon Blade / Kirin Tor helms.
|
||||
// Item ids from db_world item_template; tweak counts for your fork.
|
||||
static constexpr uint32 kGemStack = 20;
|
||||
static constexpr uint32 kGemStackMed = 12;
|
||||
static constexpr uint32 kGemStackSmall = 8;
|
||||
static constexpr uint32 kScrollPair = 2;
|
||||
static constexpr uint32 kMetaCount = 3;
|
||||
static constexpr uint32 kBeltBuckle = 4;
|
||||
static constexpr uint32 kLegKit = 4;
|
||||
static constexpr uint32 kAugmentPair = 2;
|
||||
static constexpr uint32 kScopeKit = 4;
|
||||
|
||||
static constexpr ParagonTesterStackedGrant kTesterGemsStr[] = {
|
||||
{ 40111, kGemStack }, { 40117, kGemStack }, { 40114, kGemStack }, { 40116, kGemStackMed }, { 40118, kGemStackMed },
|
||||
{ 40119, kGemStackSmall }, { 40142, kGemStackMed }, { 40143, kGemStackMed }, { 40153, kGemStackMed }, { 40162, kGemStackMed },
|
||||
{ 41285, kMetaCount }, { 41398, 2 },
|
||||
{ 44493, kScrollPair }, { 44815, kScrollPair }, { 44465, kScrollPair }, { 39006, kScrollPair }, { 39003, kScrollPair },
|
||||
{ 44458, kScrollPair }, { 41611, kBeltBuckle }, { 38374, kLegKit }, { 50335, kAugmentPair }, { 50367, kAugmentPair },
|
||||
};
|
||||
|
||||
static constexpr ParagonTesterStackedGrant kTesterGemsAgi[] = {
|
||||
{ 40112, kGemStack }, { 40117, kGemStack }, { 40114, kGemStackMed }, { 40142, kGemStackMed }, { 40152, kGemStackMed },
|
||||
{ 40153, kGemStackMed }, { 40155, kGemStackMed }, { 40125, kGemStackMed }, { 41398, kMetaCount }, { 41285, 2 },
|
||||
{ 44493, kScrollPair }, { 44815, kScrollPair }, { 44465, kScrollPair }, { 39006, kScrollPair }, { 39003, kScrollPair },
|
||||
{ 44458, kScrollPair }, { 38986, kScrollPair }, { 41611, kBeltBuckle }, { 38373, kLegKit }, { 50335, kAugmentPair }, { 50367, kAugmentPair },
|
||||
};
|
||||
|
||||
static constexpr ParagonTesterStackedGrant kTesterGemsInt[] = {
|
||||
{ 40113, kGemStack }, { 40155, kGemStackMed }, { 40153, kGemStackMed }, { 40133, kGemStackMed }, { 40119, kGemStackSmall },
|
||||
{ 40125, kGemStackMed }, { 41285, kMetaCount },
|
||||
{ 44467, kScrollPair }, { 44470, kScrollPair }, { 38979, kScrollPair }, { 39003, kScrollPair }, { 39006, kScrollPair },
|
||||
{ 44465, kScrollPair }, { 38973, kScrollPair }, { 41611, kBeltBuckle }, { 41602, kLegKit }, { 50338, kAugmentPair }, { 50368, kAugmentPair },
|
||||
};
|
||||
|
||||
static constexpr ParagonTesterStackedGrant kTesterGemsSpi[] = {
|
||||
{ 40113, kGemStackMed }, { 40133, kGemStack }, { 40120, kGemStackMed }, { 40119, kGemStackSmall }, { 40155, kGemStackMed },
|
||||
{ 41285, kMetaCount },
|
||||
{ 44470, kScrollPair }, { 38853, kScrollPair }, { 38961, kScrollPair }, { 38979, kScrollPair }, { 39006, kScrollPair },
|
||||
{ 44465, kScrollPair }, { 41611, kBeltBuckle }, { 41601, kLegKit }, { 50336, kAugmentPair }, { 50370, kAugmentPair },
|
||||
};
|
||||
|
||||
static constexpr ParagonTesterStackedGrant kTesterGemsTank[] = {
|
||||
{ 40119, kGemStack }, { 40138, kGemStackMed }, { 40115, kGemStackMed }, { 40118, kGemStackMed }, { 40143, kGemStackMed },
|
||||
{ 41285, 2 },
|
||||
{ 38945, kScrollPair }, { 44489, kScrollPair }, { 38849, kScrollPair }, { 39006, kScrollPair }, { 44465, kScrollPair },
|
||||
{ 41611, kBeltBuckle }, { 38373, kLegKit }, { 50337, kAugmentPair }, { 50369, kAugmentPair },
|
||||
};
|
||||
|
||||
static constexpr ParagonTesterStackedGrant kTesterGemsHybrid[] = {
|
||||
{ 40113, kGemStackMed }, { 40111, kGemStackMed }, { 40114, kGemStackMed }, { 40153, kGemStackMed }, { 40142, kGemStackMed },
|
||||
{ 40155, kGemStackMed }, { 41285, kMetaCount }, { 41398, 2 },
|
||||
{ 44467, kScrollPair }, { 44493, kScrollPair }, { 44815, kScrollPair }, { 44470, kScrollPair }, { 44465, kScrollPair },
|
||||
{ 39006, kScrollPair }, { 39003, kScrollPair }, { 41611, kBeltBuckle }, { 41602, 2 }, { 38373, 2 }, { 38374, 2 },
|
||||
{ 50338, kAugmentPair }, { 50335, kAugmentPair }, { 50368, 1 }, { 50367, 1 },
|
||||
};
|
||||
|
||||
// Hunter / physical ranged: scopes (engineering attach) + hit/agi gems + physical scrolls.
|
||||
static constexpr ParagonTesterStackedGrant kTesterGemsRanged[] = {
|
||||
{ 44739, kScopeKit }, { 41167, kScopeKit }, { 41146, 2 },
|
||||
{ 40112, kGemStack }, { 40117, kGemStack }, { 40125, kGemStack }, { 40142, kGemStackMed }, { 40152, kGemStackMed },
|
||||
{ 40153, kGemStackMed }, { 41398, kMetaCount }, { 41285, 2 },
|
||||
{ 44493, kScrollPair }, { 44815, kScrollPair }, { 44465, kScrollPair }, { 39006, kScrollPair }, { 39003, kScrollPair },
|
||||
{ 44458, kScrollPair }, { 38986, kScrollPair }, { 41611, kBeltBuckle }, { 38373, kLegKit }, { 50335, kAugmentPair }, { 50367, kAugmentPair },
|
||||
};
|
||||
|
||||
class Paragon_Essence_CommandScript : public CommandScript
|
||||
{
|
||||
public:
|
||||
@@ -4599,6 +5084,38 @@ public:
|
||||
|
||||
ChatCommandTable GetCommands() const override
|
||||
{
|
||||
static ChatCommandTable testerBisGemsTable =
|
||||
{
|
||||
{ "str", HandleTesterBisGemsStr, rbac::RBAC_PERM_COMMAND_ADDITEM, Console::No },
|
||||
{ "agi", HandleTesterBisGemsAgi, rbac::RBAC_PERM_COMMAND_ADDITEM, Console::No },
|
||||
{ "int", HandleTesterBisGemsInt, rbac::RBAC_PERM_COMMAND_ADDITEM, Console::No },
|
||||
{ "spi", HandleTesterBisGemsSpi, rbac::RBAC_PERM_COMMAND_ADDITEM, Console::No },
|
||||
{ "tank", HandleTesterBisGemsTank, rbac::RBAC_PERM_COMMAND_ADDITEM, Console::No },
|
||||
{ "hybrid", HandleTesterBisGemsHybrid, rbac::RBAC_PERM_COMMAND_ADDITEM, Console::No },
|
||||
{ "ranged", HandleTesterBisGemsRanged, rbac::RBAC_PERM_COMMAND_ADDITEM, Console::No },
|
||||
{ "", HandleTesterBisGemsHelp, rbac::RBAC_PERM_COMMAND_ADDITEM, Console::No },
|
||||
};
|
||||
|
||||
static ChatCommandTable testerBisTable =
|
||||
{
|
||||
{ "str", HandleTesterBisStr, rbac::RBAC_PERM_COMMAND_ADDITEM, Console::No },
|
||||
{ "agi", HandleTesterBisAgi, rbac::RBAC_PERM_COMMAND_ADDITEM, Console::No },
|
||||
{ "int", HandleTesterBisInt, rbac::RBAC_PERM_COMMAND_ADDITEM, Console::No },
|
||||
{ "spi", HandleTesterBisSpi, rbac::RBAC_PERM_COMMAND_ADDITEM, Console::No },
|
||||
{ "tank", HandleTesterBisTank, rbac::RBAC_PERM_COMMAND_ADDITEM, Console::No },
|
||||
{ "hybrid", HandleTesterBisHybrid, rbac::RBAC_PERM_COMMAND_ADDITEM, Console::No },
|
||||
{ "hunter", HandleTesterBisHunter, rbac::RBAC_PERM_COMMAND_ADDITEM, Console::No },
|
||||
{ "gems", testerBisGemsTable },
|
||||
};
|
||||
|
||||
static ChatCommandTable testerTable =
|
||||
{
|
||||
{ "bis", testerBisTable },
|
||||
{ "bags", HandleTesterBags, rbac::RBAC_PERM_COMMAND_ADDITEM, Console::No },
|
||||
{ "weapons", HandleTesterWeapons, rbac::RBAC_PERM_COMMAND_ADDITEM, Console::No },
|
||||
{ "clearinv", HandleTesterClearInv, rbac::RBAC_PERM_COMMAND_ADDITEM, Console::No },
|
||||
};
|
||||
|
||||
static ChatCommandTable paragonSubTable =
|
||||
{
|
||||
{ "currency", HandleCurrency, rbac::RBAC_PERM_COMMAND_LEARN, Console::No },
|
||||
@@ -4606,6 +5123,7 @@ public:
|
||||
{ "runes", HandleRunes, rbac::RBAC_PERM_COMMAND_LEARN, Console::No },
|
||||
{ "hat", HandleHat, rbac::RBAC_PERM_COMMAND_LEARN, Console::No },
|
||||
{ "recalibrate", HandlePanelRecalibrate, rbac::RBAC_PERM_COMMAND_MODIFY, Console::No },
|
||||
{ "tester", testerTable },
|
||||
};
|
||||
|
||||
static ChatCommandTable commandTable =
|
||||
@@ -4616,6 +5134,238 @@ public:
|
||||
return commandTable;
|
||||
}
|
||||
|
||||
static bool HandleTesterBisKit(ChatHandler* handler, uint32 const* ids, size_t count, char const* label)
|
||||
{
|
||||
Player* target = handler->getSelectedPlayerOrSelf();
|
||||
if (!target)
|
||||
{
|
||||
handler->SendErrorMessage(LANG_PLAYER_NOT_FOUND);
|
||||
return false;
|
||||
}
|
||||
|
||||
uint32 const n = ParagonTesterGrantItemList(target, ids, count, handler);
|
||||
handler->PSendSysMessage("Paragon tester {} BiS: granted {} items to {}.", label, n, target->GetName());
|
||||
return n > 0;
|
||||
}
|
||||
|
||||
static bool HandleTesterBisStr(ChatHandler* handler)
|
||||
{
|
||||
return HandleTesterBisKit(handler, kTesterBisStr, sizeof(kTesterBisStr) / sizeof(kTesterBisStr[0]), "STR");
|
||||
}
|
||||
|
||||
static bool HandleTesterBisAgi(ChatHandler* handler)
|
||||
{
|
||||
return HandleTesterBisKit(handler, kTesterBisAgi, sizeof(kTesterBisAgi) / sizeof(kTesterBisAgi[0]), "AGI");
|
||||
}
|
||||
|
||||
static bool HandleTesterBisInt(ChatHandler* handler)
|
||||
{
|
||||
return HandleTesterBisKit(handler, kTesterBisInt, sizeof(kTesterBisInt) / sizeof(kTesterBisInt[0]), "INT");
|
||||
}
|
||||
|
||||
static bool HandleTesterBisSpi(ChatHandler* handler)
|
||||
{
|
||||
return HandleTesterBisKit(handler, kTesterBisSpi, sizeof(kTesterBisSpi) / sizeof(kTesterBisSpi[0]), "SPI");
|
||||
}
|
||||
|
||||
static bool HandleTesterBisTank(ChatHandler* handler)
|
||||
{
|
||||
return HandleTesterBisKit(handler, kTesterBisTank, sizeof(kTesterBisTank) / sizeof(kTesterBisTank[0]), "tank");
|
||||
}
|
||||
|
||||
static bool HandleTesterBisHybrid(ChatHandler* handler)
|
||||
{
|
||||
return HandleTesterBisKit(handler, kTesterBisHybrid, sizeof(kTesterBisHybrid) / sizeof(kTesterBisHybrid[0]), "hybrid");
|
||||
}
|
||||
|
||||
static bool HandleTesterBisHunter(ChatHandler* handler)
|
||||
{
|
||||
return HandleTesterBisKit(handler, kTesterBisHunter, sizeof(kTesterBisHunter) / sizeof(kTesterBisHunter[0]), "hunter (ranged)");
|
||||
}
|
||||
|
||||
static bool HandleTesterBisGemsHelp(ChatHandler* handler)
|
||||
{
|
||||
handler->SendSysMessage(
|
||||
"Paragon tester gems: .paragon tester bis gems <category>\n"
|
||||
" category: str | agi | int | spi | tank | hybrid | ranged\n"
|
||||
" Grants stacked ICC-era gems, enchant scrolls, Eternal Belt Buckle, leg armor/spellthread, "
|
||||
"Sons of Hodir shoulder inscriptions, and helm arcanums. "
|
||||
"ranged adds engineering scopes (Diamond-cut Refractor, Heartseeker, Sun) plus AGI/hit gems.");
|
||||
return false;
|
||||
}
|
||||
|
||||
static bool HandleTesterBisGemsKit(ChatHandler* handler, ParagonTesterStackedGrant const* grants, size_t count, char const* label)
|
||||
{
|
||||
Player* target = handler->getSelectedPlayerOrSelf();
|
||||
if (!target)
|
||||
{
|
||||
handler->SendErrorMessage(LANG_PLAYER_NOT_FOUND);
|
||||
return false;
|
||||
}
|
||||
|
||||
uint32 const n = ParagonTesterGrantStackedItemList(target, grants, count, handler);
|
||||
handler->PSendSysMessage("Paragon tester {} gems/enchants: granted {} item pieces (stacked lines) to {}.",
|
||||
label,
|
||||
n,
|
||||
target->GetName());
|
||||
return n > 0;
|
||||
}
|
||||
|
||||
static bool HandleTesterBisGemsStr(ChatHandler* handler)
|
||||
{
|
||||
return HandleTesterBisGemsKit(handler, kTesterGemsStr, sizeof(kTesterGemsStr) / sizeof(kTesterGemsStr[0]), "STR");
|
||||
}
|
||||
|
||||
static bool HandleTesterBisGemsAgi(ChatHandler* handler)
|
||||
{
|
||||
return HandleTesterBisGemsKit(handler, kTesterGemsAgi, sizeof(kTesterGemsAgi) / sizeof(kTesterGemsAgi[0]), "AGI");
|
||||
}
|
||||
|
||||
static bool HandleTesterBisGemsInt(ChatHandler* handler)
|
||||
{
|
||||
return HandleTesterBisGemsKit(handler, kTesterGemsInt, sizeof(kTesterGemsInt) / sizeof(kTesterGemsInt[0]), "INT");
|
||||
}
|
||||
|
||||
static bool HandleTesterBisGemsSpi(ChatHandler* handler)
|
||||
{
|
||||
return HandleTesterBisGemsKit(handler, kTesterGemsSpi, sizeof(kTesterGemsSpi) / sizeof(kTesterGemsSpi[0]), "SPI");
|
||||
}
|
||||
|
||||
static bool HandleTesterBisGemsTank(ChatHandler* handler)
|
||||
{
|
||||
return HandleTesterBisGemsKit(handler, kTesterGemsTank, sizeof(kTesterGemsTank) / sizeof(kTesterGemsTank[0]), "tank");
|
||||
}
|
||||
|
||||
static bool HandleTesterBisGemsHybrid(ChatHandler* handler)
|
||||
{
|
||||
return HandleTesterBisGemsKit(handler, kTesterGemsHybrid, sizeof(kTesterGemsHybrid) / sizeof(kTesterGemsHybrid[0]), "hybrid");
|
||||
}
|
||||
|
||||
static bool HandleTesterBisGemsRanged(ChatHandler* handler)
|
||||
{
|
||||
return HandleTesterBisGemsKit(handler, kTesterGemsRanged, sizeof(kTesterGemsRanged) / sizeof(kTesterGemsRanged[0]), "RANGED");
|
||||
}
|
||||
|
||||
static bool HandleTesterBags(ChatHandler* handler)
|
||||
{
|
||||
Player* target = handler->getSelectedPlayerOrSelf();
|
||||
if (!target)
|
||||
{
|
||||
handler->SendErrorMessage(LANG_PLAYER_NOT_FOUND);
|
||||
return false;
|
||||
}
|
||||
|
||||
uint32 const bagId = ParagonTesterSelectLargestUsableBagItemId(target);
|
||||
if (!bagId)
|
||||
{
|
||||
handler->SendSysMessage("Paragon tester bags: no bag template this character can use (check item ids).");
|
||||
return false;
|
||||
}
|
||||
|
||||
uint32 granted = 0;
|
||||
uint32 equipped = 0;
|
||||
for (int i = 0; i < 4; ++i)
|
||||
{
|
||||
ItemPosCountVec dest;
|
||||
if (target->CanStoreNewItem(NULL_BAG, NULL_SLOT, dest, bagId, 1) != EQUIP_ERR_OK || dest.empty())
|
||||
{
|
||||
handler->PSendSysMessage("Paragon tester bags: could only add {} bag(s); inventory full?", granted);
|
||||
break;
|
||||
}
|
||||
|
||||
if (Item* item = target->StoreNewItem(dest, bagId, true))
|
||||
{
|
||||
item->SetBinding(false);
|
||||
++granted;
|
||||
if (ParagonTesterTryEquipBagToFirstEmptySlot(target, item))
|
||||
++equipped;
|
||||
}
|
||||
}
|
||||
|
||||
ItemTemplate const* proto = sObjectMgr->GetItemTemplate(bagId);
|
||||
handler->PSendSysMessage("Paragon tester bags: added {} x {} ({}); auto-equipped {} to bag bar for {}.",
|
||||
granted,
|
||||
bagId,
|
||||
proto ? proto->Name1.c_str() : "?",
|
||||
equipped,
|
||||
target->GetName());
|
||||
return granted > 0;
|
||||
}
|
||||
|
||||
static bool HandleTesterWeapons(ChatHandler* handler, Tail tail)
|
||||
{
|
||||
std::string_view sv = tail;
|
||||
while (!sv.empty() && sv.front() == ' ')
|
||||
sv.remove_prefix(1);
|
||||
|
||||
if (sv.empty())
|
||||
{
|
||||
handler->SendSysMessage(
|
||||
"Paragon tester weapons: .paragon tester weapons <stat> <type>\n"
|
||||
" stat: str | agi | int | spi | tank | hybrid (aliases: strength, agility, intellect, spirit, apsp)\n"
|
||||
" type: depends on stat — e.g. str: 2h sword, 2h axe, 2h mace, 1h sword, 1h axe, 1h mace (or 2h/sword), dual, ranged | "
|
||||
"agi: … bow (hunter bow) or ranged/gun/crossbow (Fal'inrush) | int: staff, wand, mhoh, shield, ranged | "
|
||||
"spi/hybrid: … ranged");
|
||||
return false;
|
||||
}
|
||||
|
||||
std::size_t const sp = sv.find(' ');
|
||||
if (sp == std::string_view::npos)
|
||||
{
|
||||
handler->SendSysMessage("Paragon tester weapons: need both <stat> and <type> (see help with no args).");
|
||||
return false;
|
||||
}
|
||||
|
||||
std::string stat(sv.substr(0, sp));
|
||||
sv.remove_prefix(sp + 1);
|
||||
while (!sv.empty() && sv.front() == ' ')
|
||||
sv.remove_prefix(1);
|
||||
if (sv.empty())
|
||||
{
|
||||
handler->SendSysMessage("Paragon tester weapons: missing <type> after stat.");
|
||||
return false;
|
||||
}
|
||||
std::string weaponType(sv.begin(), sv.end());
|
||||
|
||||
Player* target = handler->getSelectedPlayerOrSelf();
|
||||
if (!target)
|
||||
{
|
||||
handler->SendErrorMessage(LANG_PLAYER_NOT_FOUND);
|
||||
return false;
|
||||
}
|
||||
|
||||
std::vector<uint32> ids;
|
||||
std::string err;
|
||||
if (!ParagonTesterResolveWeaponKit(stat, weaponType, ids, err))
|
||||
{
|
||||
handler->PSendSysMessage("Paragon tester weapons: {}", err);
|
||||
return false;
|
||||
}
|
||||
|
||||
uint32 const n = ParagonTesterGrantItemList(target, ids.data(), ids.size(), handler);
|
||||
handler->PSendSysMessage("Paragon tester weapons [{} / {}]: granted {} item(s) to {}.",
|
||||
stat,
|
||||
weaponType,
|
||||
n,
|
||||
target->GetName());
|
||||
return n > 0;
|
||||
}
|
||||
|
||||
static bool HandleTesterClearInv(ChatHandler* handler)
|
||||
{
|
||||
Player* target = handler->getSelectedPlayerOrSelf();
|
||||
if (!target)
|
||||
{
|
||||
handler->SendErrorMessage(LANG_PLAYER_NOT_FOUND);
|
||||
return false;
|
||||
}
|
||||
|
||||
ParagonTesterClearNonEquipmentInventory(target);
|
||||
handler->PSendSysMessage("Paragon tester clearinv: removed backpack + bag contents (equipment untouched) for {}.",
|
||||
target->GetName());
|
||||
return true;
|
||||
}
|
||||
|
||||
// Full Character Advancement reset for the selected player (or self):
|
||||
// unlearn all panel spells/talents, clear panel DB + active build pointer,
|
||||
// then clamp AE/TE to the level-correct totals (same math as login
|
||||
|
||||
@@ -2260,9 +2260,9 @@ Achievement.RealmFirstKillWindow = 60
|
||||
# MaxPrimaryTradeSkill
|
||||
# Description: Maximum number of primary professions a character can learn.
|
||||
# Range: 0-11
|
||||
# Default: 2
|
||||
# Default: 11 - (All WotLK primary professions; set 2 for retail-like two-slot cap.)
|
||||
|
||||
MaxPrimaryTradeSkill = 2
|
||||
MaxPrimaryTradeSkill = 11
|
||||
|
||||
#
|
||||
# SkillChance.Prospecting
|
||||
|
||||
@@ -678,6 +678,7 @@ enum RBACPermissions
|
||||
RBAC_PERM_COMMAND_BF_QUEUE = 913,
|
||||
RBAC_PERM_COMMAND_PET_LIST = 914,
|
||||
RBAC_PERM_COMMAND_PET_DELETE = 915,
|
||||
RBAC_PERM_COMMAND_LEARN_ALL_MOUNTS = 916,
|
||||
// custom permissions 1000+
|
||||
RBAC_PERM_MAX
|
||||
};
|
||||
|
||||
@@ -3640,6 +3640,13 @@ bool Creature::IsMovementPreventedByCasting() const
|
||||
return false;
|
||||
}
|
||||
|
||||
// Fractured: cast-time mount summon (player-style mount spells on NPCs are rare but supported).
|
||||
if (Spell* genSpell = m_currentSpells[CURRENT_GENERIC_SPELL])
|
||||
{
|
||||
if (genSpell->getState() == SPELL_STATE_PREPARING && genSpell->m_spellInfo->IsCastTimeRidingMountSpell())
|
||||
return false;
|
||||
}
|
||||
|
||||
if (HasSpellFocus())
|
||||
{
|
||||
return true;
|
||||
|
||||
@@ -1828,6 +1828,7 @@ public:
|
||||
uint32 GetLastPotionId() { return m_lastPotionId; }
|
||||
void SetLastPotionId(uint32 item_id) { m_lastPotionId = item_id; }
|
||||
void UpdatePotionCooldown(Spell* spell = nullptr);
|
||||
void AtEnterCombat() override;
|
||||
void AtExitCombat() override;
|
||||
|
||||
void setResurrectRequestData(ObjectGuid guid, uint32 mapId, float X, float Y, float Z, uint32 health, uint32 mana)
|
||||
|
||||
@@ -32,6 +32,7 @@
|
||||
#include "Player.h"
|
||||
#include "ScriptMgr.h"
|
||||
#include "SkillDiscovery.h"
|
||||
#include "Spell.h"
|
||||
#include "SpellAuraEffects.h"
|
||||
#include "SpellMgr.h"
|
||||
#include "UpdateFieldFlags.h"
|
||||
@@ -1561,6 +1562,27 @@ void Player::UpdatePvP(bool state, bool _override)
|
||||
sScriptMgr->OnPlayerPVPFlagChange(this, state);
|
||||
}
|
||||
|
||||
void Player::AtEnterCombat()
|
||||
{
|
||||
Unit::AtEnterCombat();
|
||||
|
||||
// Fractured: cancel cast-time mount summon if combat starts mid-cast.
|
||||
for (uint32 spellType = CURRENT_FIRST_NON_MELEE_SPELL; spellType < CURRENT_MAX_SPELL; ++spellType)
|
||||
{
|
||||
if (Spell* spell = GetCurrentSpell(CurrentSpellTypes(spellType)))
|
||||
{
|
||||
if (SpellInfo const* info = spell->GetSpellInfo())
|
||||
{
|
||||
if (info->IsCastTimeRidingMountSpell())
|
||||
{
|
||||
InterruptSpell(CurrentSpellTypes(spellType), false, false);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void Player::AtExitCombat()
|
||||
{
|
||||
Unit::AtExitCombat();
|
||||
|
||||
@@ -4503,6 +4503,13 @@ bool Unit::IsMovementPreventedByCasting() const
|
||||
return false;
|
||||
}
|
||||
|
||||
// Fractured: cast-time mount summon may be completed while moving.
|
||||
if (Spell* genSpell = m_currentSpells[CURRENT_GENERIC_SPELL])
|
||||
{
|
||||
if (genSpell->getState() == SPELL_STATE_PREPARING && genSpell->m_spellInfo->IsCastTimeRidingMountSpell())
|
||||
return false;
|
||||
}
|
||||
|
||||
// channeled spells during channel stage (after the initial cast timer) allow movement with a specific spell attribute
|
||||
if (Spell* spell = m_currentSpells[CURRENT_CHANNELED_SPELL])
|
||||
{
|
||||
|
||||
@@ -3589,14 +3589,18 @@ SpellCastResult Spell::prepare(SpellCastTargets const* targets, AuraEffect const
|
||||
|
||||
// don't allow channeled spells / spells with cast time to be casted while moving
|
||||
// (even if they are interrupted on moving, spells with almost immediate effect get to have their effect processed before movement interrupter kicks in)
|
||||
// Fractured: cast-time mount summons (SPELL_AURA_MOUNTED + non-zero base cast) may be started while moving.
|
||||
if ((m_spellInfo->IsChanneled() || m_casttime) && m_caster->IsPlayer() && m_caster->isMoving() && m_spellInfo->InterruptFlags & SPELL_INTERRUPT_FLAG_MOVEMENT && !IsTriggered())
|
||||
{
|
||||
// 1. Has casttime, 2. Or doesn't have flag to allow action during channel
|
||||
if (m_casttime || !m_spellInfo->IsActionAllowedChannel())
|
||||
if (!m_spellInfo->IsCastTimeRidingMountSpell())
|
||||
{
|
||||
SendCastResult(SPELL_FAILED_MOVING);
|
||||
finish(false);
|
||||
return SPELL_FAILED_MOVING;
|
||||
// 1. Has casttime, 2. Or doesn't have flag to allow action during channel
|
||||
if (m_casttime || !m_spellInfo->IsActionAllowedChannel())
|
||||
{
|
||||
SendCastResult(SPELL_FAILED_MOVING);
|
||||
finish(false);
|
||||
return SPELL_FAILED_MOVING;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4436,9 +4440,11 @@ void Spell::update(uint32 difftime)
|
||||
|
||||
// check if the player caster has moved before the spell finished
|
||||
// xinef: added preparing state (real cast, skip channels as they have other flags for this)
|
||||
// Fractured: cast-time mount summons are allowed to complete while moving.
|
||||
if ((m_caster->IsPlayer() && m_timer != 0) &&
|
||||
m_caster->isMoving() && (m_spellInfo->InterruptFlags & SPELL_INTERRUPT_FLAG_MOVEMENT) && m_spellState == SPELL_STATE_PREPARING &&
|
||||
(m_spellInfo->Effects[0].Effect != SPELL_EFFECT_STUCK || !m_caster->HasUnitMovementFlag(MOVEMENTFLAG_FALLING_FAR)))
|
||||
(m_spellInfo->Effects[0].Effect != SPELL_EFFECT_STUCK || !m_caster->HasUnitMovementFlag(MOVEMENTFLAG_FALLING_FAR)) &&
|
||||
!m_spellInfo->IsCastTimeRidingMountSpell())
|
||||
{
|
||||
// don't cancel for melee, autorepeat, triggered and instant spells
|
||||
if (!IsNextMeleeSwingSpell() && !IsAutoRepeat() && !IsTriggered())
|
||||
@@ -5815,6 +5821,10 @@ SpellCastResult Spell::CheckCast(bool strict, uint32* /*param1*/, uint32* /*para
|
||||
|
||||
if (reqCombat && m_caster->IsInCombat() && !m_spellInfo->CanBeUsedInCombat())
|
||||
return SPELL_FAILED_AFFECTING_COMBAT;
|
||||
|
||||
// Fractured: cast-time mount summons cannot be used while in combat (even if DBC omits NOT_IN_COMBAT_ONLY_PEACEFUL).
|
||||
if (reqCombat && m_caster->IsInCombat() && m_spellInfo->IsCastTimeRidingMountSpell())
|
||||
return SPELL_FAILED_AFFECTING_COMBAT;
|
||||
}
|
||||
|
||||
// Xinef: exploit protection
|
||||
@@ -5842,10 +5852,14 @@ SpellCastResult Spell::CheckCast(bool strict, uint32* /*param1*/, uint32* /*para
|
||||
// (not wand currently autorepeat cast delayed to moving stop anyway in spell update code)
|
||||
if (m_caster->IsPlayer() && m_caster->ToPlayer()->isMoving() && !IsTriggered())
|
||||
{
|
||||
// skip stuck spell to allow use it in falling case and apply spell limitations at movement
|
||||
if ((!m_caster->HasUnitMovementFlag(MOVEMENTFLAG_FALLING_FAR) || m_spellInfo->Effects[0].Effect != SPELL_EFFECT_STUCK) &&
|
||||
(IsAutoRepeat() || (m_spellInfo->AuraInterruptFlags & AURA_INTERRUPT_FLAG_NOT_SEATED) != 0))
|
||||
return SPELL_FAILED_MOVING;
|
||||
// Fractured: cast-time mount summons may be started while moving.
|
||||
if (!m_spellInfo->IsCastTimeRidingMountSpell())
|
||||
{
|
||||
// skip stuck spell to allow use it in falling case and apply spell limitations at movement
|
||||
if ((!m_caster->HasUnitMovementFlag(MOVEMENTFLAG_FALLING_FAR) || m_spellInfo->Effects[0].Effect != SPELL_EFFECT_STUCK) &&
|
||||
(IsAutoRepeat() || (m_spellInfo->AuraInterruptFlags & AURA_INTERRUPT_FLAG_NOT_SEATED) != 0))
|
||||
return SPELL_FAILED_MOVING;
|
||||
}
|
||||
}
|
||||
|
||||
Vehicle* vehicle = m_caster->GetVehicle();
|
||||
|
||||
@@ -909,6 +909,15 @@ bool SpellInfo::HasAura(AuraType aura) const
|
||||
return false;
|
||||
}
|
||||
|
||||
bool SpellInfo::IsCastTimeRidingMountSpell() const
|
||||
{
|
||||
if (IsChanneled())
|
||||
return false;
|
||||
if (!HasAura(SPELL_AURA_MOUNTED))
|
||||
return false;
|
||||
return CalcCastTime(nullptr, nullptr) > 0;
|
||||
}
|
||||
|
||||
bool SpellInfo::HasAnyAura() const
|
||||
{
|
||||
for (uint8 i = 0; i < MAX_SPELL_EFFECTS; ++i)
|
||||
|
||||
@@ -434,6 +434,9 @@ public:
|
||||
bool HasEffect(SpellEffects effect) const;
|
||||
bool HasEffectMechanic(Mechanics mechanic) const;
|
||||
bool HasAura(AuraType aura) const;
|
||||
/// Summon mount aura (SPELL_AURA_MOUNTED) with a non-zero base cast time from SpellCastTimes.dbc.
|
||||
/// Used by Fractured mount rules: castable while moving, never in combat, interrupted on combat enter.
|
||||
bool IsCastTimeRidingMountSpell() const;
|
||||
bool HasAnyAura() const;
|
||||
bool HasAreaAuraEffect() const;
|
||||
bool HasOnlyDamageEffects() const;
|
||||
|
||||
@@ -270,7 +270,8 @@ void WorldConfig::BuildConfigCache()
|
||||
SetConfigValue<uint32>(CONFIG_INSTANCE_RESET_TIME_RELATIVE_TIMESTAMP, "Instance.ResetTimeRelativeTimestamp", 1135814400);
|
||||
SetConfigValue<uint32>(CONFIG_INSTANCE_UNLOAD_DELAY, "Instance.UnloadDelay", 1800000);
|
||||
|
||||
SetConfigValue<uint32>(CONFIG_MAX_PRIMARY_TRADE_SKILL, "MaxPrimaryTradeSkill", 2);
|
||||
// WotLK has 11 primary profession skill lines (gathering + crafting); secondary (Cooking, Fishing, First Aid) are not limited here.
|
||||
SetConfigValue<uint32>(CONFIG_MAX_PRIMARY_TRADE_SKILL, "MaxPrimaryTradeSkill", 11);
|
||||
SetConfigValue<uint32>(CONFIG_MIN_PETITION_SIGNS, "MinPetitionSigns", 9, ConfigValueCache::Reloadable::Yes, [](uint32 const& value) { return value <= 9; }, "<= 9");
|
||||
|
||||
SetConfigValue<uint32>(CONFIG_GM_LOGIN_STATE, "GM.LoginState", 2);
|
||||
|
||||
@@ -16,6 +16,7 @@
|
||||
*/
|
||||
|
||||
#include "CommandScript.h"
|
||||
#include "DBCStores.h"
|
||||
#include "Language.h"
|
||||
#include "ObjectMgr.h"
|
||||
#include "Pet.h"
|
||||
@@ -51,6 +52,7 @@ public:
|
||||
{ "default", HandleLearnAllDefaultCommand, rbac::RBAC_PERM_COMMAND_LEARN_ALL_DEFAULT, Console::No },
|
||||
{ "lang", HandleLearnAllLangCommand, rbac::RBAC_PERM_COMMAND_LEARN_ALL_LANG, Console::No },
|
||||
{ "recipes", HandleLearnAllRecipesCommand, rbac::RBAC_PERM_COMMAND_LEARN_ALL_RECIPES, Console::No },
|
||||
{ "mounts", HandleLearnAllMountsCommand, rbac::RBAC_PERM_COMMAND_LEARN_ALL_MOUNTS, Console::No },
|
||||
};
|
||||
|
||||
static ChatCommandTable learnCommandTable =
|
||||
@@ -386,6 +388,66 @@ public:
|
||||
return true;
|
||||
}
|
||||
|
||||
static void SetRidingSkillToMaxForPlayer(Player* player)
|
||||
{
|
||||
SkillRaceClassInfoEntry const* rcInfo = GetSkillRaceClassInfo(SKILL_RIDING, player->getRace(), player->getClass());
|
||||
if (!rcInfo || GetSkillRangeType(rcInfo) != SKILL_RANGE_RANK)
|
||||
return;
|
||||
|
||||
SkillTiersEntry const* tier = sSkillTiersStore.LookupEntry(rcInfo->SkillTierID);
|
||||
if (!tier)
|
||||
return;
|
||||
|
||||
uint8 rank = 0;
|
||||
uint16 maxValue = 0;
|
||||
for (uint8 i = 0; i < MAX_SKILL_STEP; ++i)
|
||||
{
|
||||
if (tier->Value[i] == 0)
|
||||
continue;
|
||||
rank = i + 1;
|
||||
maxValue = tier->Value[i];
|
||||
}
|
||||
|
||||
if (!rank || !maxValue)
|
||||
return;
|
||||
|
||||
player->SetSkill(SKILL_RIDING, rank, maxValue, maxValue);
|
||||
}
|
||||
|
||||
static bool HandleLearnAllMountsCommand(ChatHandler* handler)
|
||||
{
|
||||
Player* target = handler->getSelectedPlayer();
|
||||
if (!target)
|
||||
{
|
||||
handler->SendSysMessage(LANG_PLAYER_NOT_FOUND);
|
||||
return false;
|
||||
}
|
||||
|
||||
SetRidingSkillToMaxForPlayer(target);
|
||||
handler->PSendSysMessage("Set Riding skill to maximum for {}.", handler->GetNameLink(target));
|
||||
|
||||
uint32 learned = 0;
|
||||
for (uint32 i = 0; i < sSpellMgr->GetSpellInfoStoreSize(); ++i)
|
||||
{
|
||||
SpellInfo const* spellInfo = sSpellMgr->GetSpellInfo(i);
|
||||
if (!spellInfo || !SpellMgr::IsSpellValid(spellInfo))
|
||||
continue;
|
||||
|
||||
if (!spellInfo->HasAura(SPELL_AURA_MOUNTED))
|
||||
continue;
|
||||
|
||||
if (target->HasSpell(i))
|
||||
continue;
|
||||
|
||||
target->learnSpell(i, false);
|
||||
if (target->HasSpell(i))
|
||||
++learned;
|
||||
}
|
||||
|
||||
handler->PSendSysMessage("Learned {} mount spell(s) for {}.", learned, handler->GetNameLink(target));
|
||||
return true;
|
||||
}
|
||||
|
||||
static void HandleLearnSkillRecipesHelper(Player* player, uint32 skillId)
|
||||
{
|
||||
uint32 classmask = player->getClassMask();
|
||||
|
||||
Reference in New Issue
Block a user