Compare commits

...

7 Commits

Author SHA1 Message Date
Akkadius dc64561b3c [Release] 22.2.0 2023-01-27 01:13:38 -06:00
Akkadius cb2aee2713 [Release] 22.2.0 2023-01-27 01:12:30 -06:00
Chris Miles 0730b6b588 [Crash] Fix crash issue with log formatting during character creation (#2798) 2023-01-26 20:40:58 -06:00
Aeadoin 826550acac [Bots] Add EVENT_UNEQUIP_ITEM_BOT & EVENT_EQUIP_ITEM_BOT (#2796)
* Initial Commit, need to test.

* Add unequip events.

* const auto
2023-01-26 19:49:20 -05:00
Alex King b71b3f5be0 [Bots] ^create and ^viewcombos popup messages fix. (#2797)
# Notes
- These messages were showing weirdly and inconsistently, cleaned them up to show at max 4 per line.
- `^viewcombos` now shows class name and ID in the popup title.
- `^create help` now shows proper class names and IDs instead of `{}`.
2023-01-26 18:44:21 -05:00
Natedog2012 1fe79f430c [Feature] ResetItemCooldown added to lua/perl and fix item re-cast times to show properly (#2793)
* Testing.

* Add ResetItemCooldown and port it over to perl

* This flag needs to be set for updating shared item cooldowns

* Properly set item recast for all item types, on corpses, on looting

* SummonItem properly sets recast timers of summoned items

* Rename variable to avoid confusion and change manifest to be more specific

* Sanity check item_d

* Recast -1 added as RECAST_TYPE_UNLINKED_ITEM
ResetItemCooldown will still remove cooldown of item that we don't have so when we acquire it the cooldown is reset

* change magic numbers

* more magic numbers

* More constants yay

* Remove unneeded export DeleteItemRecastTimer

* Remove duplicate message, this is handled by the client in this part of the code

Co-authored-by: Kinglykrab <kinglykrab@gmail.com>
2023-01-25 20:09:08 -06:00
Alex King e5dabe0afc [Git] Add CMake Files to .gitignore (#2792)
* [Git] Add CMake Files to GitIgnore

* Update .drone.yml
2023-01-24 18:37:14 -06:00
31 changed files with 499 additions and 110 deletions
-6
View File
@@ -4,9 +4,6 @@ kind: pipeline
type: docker
name: Build Linux
clone:
depth: 1
# Limits how many of these builds can run on the drone runner at a time, this isn't about cores
concurrency:
limit: 1
@@ -43,9 +40,6 @@ name: Build Windows
concurrency:
limit: 1
clone:
depth: 1
platform:
os: windows
arch: amd64
+3
View File
@@ -68,3 +68,6 @@ compile_flags.txt
!utils/scripts/build/
!utils/scripts/build/should-release/should-release
!utils/scripts/build/should-release/should-release.exe
# CMake Files
cmake-build-relwithdebinfo/*
+23
View File
@@ -1,3 +1,26 @@
## [22.2.0] - 01/27/2023
### Bots
* Add EVENT_UNEQUIP_ITEM_BOT & EVENT_EQUIP_ITEM_BOT ([#2796](https://github.com/EQEmu/Server/pull/2796)) ([Aeadoin](https://github.com/Aeadoin)) 2023-01-27
* ^create and ^viewcombos popup messages fix. ([#2797](https://github.com/EQEmu/Server/pull/2797)) ([Kinglykrab](https://github.com/Kinglykrab)) 2023-01-26
### Code Cleanup
* Cleanup #door Command. ([#2783](https://github.com/EQEmu/Server/pull/2783)) ([Kinglykrab](https://github.com/Kinglykrab)) 2023-01-24
### Crash
* Fix crash issue with log formatting during character creation ([#2798](https://github.com/EQEmu/Server/pull/2798)) ([Akkadius](https://github.com/Akkadius)) 2023-01-27
### Feature
* ResetItemCooldown added to lua/perl and fix item re-cast times to show properly ([#2793](https://github.com/EQEmu/Server/pull/2793)) ([Natedog2012](https://github.com/Natedog2012)) 2023-01-26
### Git
* Add CMake Files to .gitignore ([#2792](https://github.com/EQEmu/Server/pull/2792)) ([Kinglykrab](https://github.com/Kinglykrab)) 2023-01-25
## [22.1.2] - 01/24/2023
### CI/CD
+2
View File
@@ -79,6 +79,8 @@
#define ANIM_DEATH 0x73
#define ANIM_LOOT 0x69
constexpr int16 RECAST_TYPE_UNLINKED_ITEM = -1;
typedef enum {
eaStanding = 0,
eaSitting, //1
+1 -1
View File
@@ -4545,7 +4545,7 @@ struct ItemVerifyReply_Struct {
struct ItemRecastDelay_Struct {
/*000*/ uint32 recast_delay; // in seconds
/*004*/ uint32 recast_type;
/*008*/ uint32 unknown008;
/*008*/ bool ignore_casting_requirement; //Ignores recast times allows items to be reset?
/*012*/
};
+6 -2
View File
@@ -721,10 +721,14 @@ bool SharedDatabase::GetInventory(uint32 char_id, EQ::InventoryProfile *inv)
inst->SetCharges(charges);
if (item->RecastDelay) {
if (timestamps.count(item->RecastType))
if (item->RecastType != RECAST_TYPE_UNLINKED_ITEM && timestamps.count(item->RecastType)) {
inst->SetRecastTimestamp(timestamps.at(item->RecastType));
else
} else if (item->RecastType == RECAST_TYPE_UNLINKED_ITEM && timestamps.count(item->ID)) {
inst->SetRecastTimestamp(timestamps.at(item->ID));
}
else {
inst->SetRecastTimestamp(0);
}
}
if (item->IsClassCommon()) {
+2 -2
View File
@@ -25,7 +25,7 @@
// Build variables
// these get injected during the build pipeline
#define CURRENT_VERSION "22.1.2-dev" // always append -dev to the current version for custom-builds
#define CURRENT_VERSION "22.2.0-dev" // always append -dev to the current version for custom-builds
#define LOGIN_VERSION "0.8.0"
#define COMPILE_DATE __DATE__
#define COMPILE_TIME __TIME__
@@ -42,7 +42,7 @@
* Manifest: https://github.com/EQEmu/Server/blob/master/utils/sql/db_update_manifest.txt
*/
#define CURRENT_BINARY_DATABASE_VERSION 9217
#define CURRENT_BINARY_DATABASE_VERSION 9218
#define CURRENT_BINARY_BOTS_DATABASE_VERSION 9037
#endif
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "eqemu-server",
"version": "22.1.2",
"version": "22.2.0",
"repository": {
"type": "git",
"url": "https://github.com/EQEmu/Server.git"
+1
View File
@@ -471,6 +471,7 @@
9215|2023_01_08_zone_max_level.sql|SHOW COLUMNS FROM `zone` LIKE 'max_level'|empty|
9216|2023_01_15_merc_data.sql|SHOW TABLES LIKE 'mercs'|empty|
9217|2023_01_15_chatchannel_reserved_names.sql|SHOW TABLES LIKE 'chatchannel_reserved_names'|empty|
9218|2023_01_24_item_recast.sql|show columns from character_item_recast like '%recast_type%'|contains|smallint
# Upgrade conditions:
# This won't be needed after this system is implemented, but it is used database that are not
@@ -0,0 +1,2 @@
ALTER TABLE `character_item_recast`
CHANGE COLUMN `recast_type` `recast_type` INT(11) UNSIGNED NOT NULL DEFAULT 0 AFTER `id`;
+33 -6
View File
@@ -1778,12 +1778,39 @@ bool Client::OPCharCreate(char *name, CharCreate_Struct *cc)
pp.binds[0].heading = pp.heading;
}
LogInfo("Current location [{}] [{}] [{}] [{}] [{}] [{}]",
ZoneName(pp.zone_id), pp.zone_id, pp.x, pp.y, pp.z, pp.heading);
LogInfo("Bind location [{}] [{}] [{}] [{}] [{}]",
ZoneName(pp.binds[0].zone_id), pp.binds[0].zone_id, pp.binds[0].x, pp.binds[0].y, pp.binds[0].z);
LogInfo("Home location [{}] [{}] [{}] [{}] [{}]",
ZoneName(pp.binds[4].zone_id), pp.binds[4].zone_id, pp.binds[4].x, pp.binds[4].y, pp.binds[4].z);
if (GetZone(pp.zone_id)) {
LogInfo(
"Current location [{}] [{}] [{:.2f}] [{:.2f}] [{:.2f}] [{:.2f}]",
ZoneName(pp.zone_id),
pp.zone_id,
pp.x,
pp.y,
pp.z,
pp.heading
);
}
if (GetZone(pp.binds[0].zone_id)) {
LogInfo(
"Bind location [{}] [{}] [{:.2f}] [{:.2f}] [{:.2f}]",
ZoneName(pp.binds[0].zone_id),
pp.binds[0].zone_id,
pp.binds[0].x,
pp.binds[0].y,
pp.binds[0].z
);
}
if (GetZone(pp.binds[4].zone_id)) {
LogInfo(
"Home location [{}] [{}] [{:.2f}] [{:.2f}] [{:.2f}]",
ZoneName(pp.binds[4].zone_id),
pp.binds[4].zone_id,
pp.binds[4].x,
pp.binds[4].y,
pp.binds[4].z
);
}
/* Starting Items inventory */
content_db.SetStartingItems(&pp, &inv, pp.race, pp.class_, pp.deity, pp.zone_id, pp.name, GetAdmin());
+16
View File
@@ -5074,6 +5074,13 @@ void Bot::PerformTradeWithClient(int16 begin_slot_id, int16 end_slot_id, Client*
BotRemoveEquipItem(return_iterator.from_bot_slot);
const auto export_string = fmt::format(
"{} {}",
return_iterator.return_item_instance->IsStackable() ? return_iterator.return_item_instance->GetCharges() : 1,
return_iterator.from_bot_slot
);
parse->EventBot(EVENT_UNEQUIP_ITEM_BOT, this, nullptr, export_string , return_iterator.return_item_instance->GetID());
if (return_instance) {
EQ::SayLinkEngine linker;
linker.SetLinkType(EQ::saylink::SayLinkItemInst);
@@ -5122,6 +5129,15 @@ void Bot::PerformTradeWithClient(int16 begin_slot_id, int16 end_slot_id, Client*
m_inv.PutItem(trade_iterator.to_bot_slot, *trade_iterator.trade_item_instance);
BotAddEquipItem(trade_iterator.to_bot_slot, (trade_iterator.trade_item_instance ? trade_iterator.trade_item_instance->GetID() : 0));
const auto export_string = fmt::format(
"{} {}",
trade_iterator.trade_item_instance->IsStackable() ? trade_iterator.trade_item_instance->GetCharges() : 1,
trade_iterator.to_bot_slot
);
parse->EventBot(EVENT_EQUIP_ITEM_BOT, this, nullptr, export_string , trade_iterator.trade_item_instance->GetID());
trade_iterator.trade_item_instance = nullptr; // actual deletion occurs in client delete below
client->DeleteItemInInventory(trade_iterator.from_client_slot, 0, (trade_iterator.from_client_slot == EQ::invslot::slotCursor));
+142 -64
View File
@@ -5374,40 +5374,54 @@ void bot_subcommand_bot_clone(Client *c, const Seperator *sep)
void bot_command_view_combos(Client *c, const Seperator *sep)
{
const std::string class_substrs[17] = { "",
"%u (WAR)", "%u (CLR)", "%u (PAL)", "%u (RNG)",
"%u (SHD)", "%u (DRU)", "%u (MNK)", "%u (BRD)",
"%u (ROG)", "%u (SHM)", "%u (NEC)", "%u (WIZ)",
"%u (MAG)", "%u (ENC)", "%u (BST)", "%u (BER)"
const std::string class_substrs[17] = {
"",
"WAR", "CLR", "PAL", "RNG",
"SHD", "DRU", "MNK", "BRD",
"ROG", "SHM", "NEC", "WIZ",
"MAG", "ENC", "BST", "BER"
};
const std::string race_substrs[17] = { "",
"%u (HUM)", "%u (BAR)", "%u (ERU)", "%u (ELF)",
"%u (HIE)", "%u (DEF)", "%u (HEF)", "%u (DWF)",
"%u (TRL)", "%u (OGR)", "%u (HFL)", "%u (GNM)",
"%u (IKS)", "%u (VAH)", "%u (FRG)", "%u (DRK)"
const std::string race_substrs[17] = {
"",
"HUM", "BAR", "ERU", "ELF",
"HIE", "DEF", "HEF", "DWF",
"TRL", "OGR", "HFL", "GNM",
"IKS", "VAH", "FRG", "DRK"
};
const uint16 race_values[17] = { 0,
HUMAN, BARBARIAN, ERUDITE, WOOD_ELF,
HIGH_ELF, DARK_ELF, HALF_ELF, DWARF,
TROLL, OGRE, HALFLING, GNOME,
IKSAR, VAHSHIR, FROGLOK, DRAKKIN
const uint16 race_values[17] = {
RACE_DOUG_0,
RACE_HUMAN_1, RACE_BARBARIAN_2, RACE_ERUDITE_3, RACE_WOOD_ELF_4,
RACE_HIGH_ELF_5, RACE_DARK_ELF_6, RACE_HALF_ELF_7, RACE_DWARF_8,
RACE_TROLL_9, RACE_OGRE_10, RACE_HALFLING_11, RACE_GNOME_12,
RACE_IKSAR_128, RACE_VAH_SHIR_130, RACE_FROGLOK_330, RACE_DRAKKIN_522
};
if (helper_command_alias_fail(c, "bot_command_view_combos", sep->arg[0], "viewcombos"))
if (helper_command_alias_fail(c, "bot_command_view_combos", sep->arg[0], "viewcombos")) {
return;
}
if (helper_is_help_or_usage(sep->arg[1])) {
std::string window_title = "Bot Races";
std::string window_text;
std::string message_separator = " ";
c->Message(Chat::White, "Usage: %s [bot_race]", sep->arg[0]);
window_text.append("<c \"#FFFFFF\">Races:<c \"#FFFF\">");
c->Message(Chat::White, fmt::format("Usage: {} [Race]", sep->arg[0]).c_str());
window_text.append("<c \"#FFFF\">");
for (int race_id = 0; race_id <= 15; ++race_id) {
window_text.append(message_separator);
window_text.append(StringFormat(race_substrs[race_id + 1].c_str(), race_values[race_id + 1]));
window_text.append(
fmt::format(
"{} ({})",
race_substrs[race_id + 1],
race_values[race_id + 1]
)
);
message_separator = ", ";
}
c->SendPopupToClient(window_title.c_str(), window_text.c_str());
c->SendPopupToClient("Bot Races", window_text.c_str());
return;
}
@@ -5415,53 +5429,92 @@ void bot_command_view_combos(Client *c, const Seperator *sep)
c->Message(Chat::White, "Invalid Race!");
return;
}
uint16 bot_race = atoi(sep->arg[1]);
auto classes_bitmask = database.botdb.GetRaceClassBitmask(bot_race);
auto race_name = GetRaceIDName(bot_race);
std::string window_title = "Bot Classes";
const uint16 bot_race = static_cast<uint16>(std::stoul(sep->arg[1]));
const std::string race_name = GetRaceIDName(bot_race);
if (!Mob::IsPlayerRace(bot_race)) {
c->Message(
Chat::White,
fmt::format(
"{} ({}) is not a race bots can use.",
race_name,
bot_race
).c_str()
);
return;
}
const auto classes_bitmask = database.botdb.GetRaceClassBitmask(bot_race);
std::string window_text;
std::string message_separator = " ";
c->Message(Chat::White, "%s can be these classes.", race_name);
window_text.append("<c \"#FFFFFF\">Classes:<c \"#FFFF\">");
window_text.append("<c \"#FFFF\">");
const int object_max = 4;
auto object_count = 0;
for (int class_id = 0; class_id <= 15; ++class_id) {
if (classes_bitmask & GetPlayerClassBit(class_id)) {
window_text.append(message_separator);
window_text.append(StringFormat(class_substrs[class_id].c_str(), class_id));
if (object_count >= object_max) {
window_text.append(DialogueWindow::Break());
object_count = 0;
}
window_text.append(
fmt::format(
"{} ({})",
class_substrs[class_id],
class_id
)
);
++object_count;
message_separator = ", ";
}
}
c->SendPopupToClient(window_title.c_str(), window_text.c_str());
return;
c->SendPopupToClient(
fmt::format(
"Bot Classes for {} ({})",
race_name,
bot_race
).c_str(),
window_text.c_str()
);
}
void bot_subcommand_bot_create(Client *c, const Seperator *sep)
{
const std::string class_substrs[17] = {
"",
"{} (WAR)", "{} (CLR)", "{} (PAL)", "{} (RNG)",
"{} (SHD)", "{} (DRU)", "{} (MNK)", "{} (BRD)",
"{} (ROG)", "{} (SHM)", "{} (NEC)", "{} (WIZ)",
"{} (MAG)", "{} (ENC)", "{} (BST)", "{} (BER)"
"WAR", "CLR", "PAL", "RNG",
"SHD", "DRU", "MNK", "BRD",
"ROG", "SHM", "NEC", "WIZ",
"MAG", "ENC", "BST", "BER"
};
const std::string race_substrs[17] = {
"",
"{} (HUM)", "{} (BAR)", "{} (ERU)", "{} (ELF)",
"{} (HIE)", "{} (DEF)", "{} (HEF)", "{} (DWF)",
"{} (TRL)", "{} (OGR)", "{} (HFL)", "{} (GNM)",
"{} (IKS)", "{} (VAH)", "{} (FRG)", "{} (DRK)"
"HUM", "BAR", "ERU", "ELF",
"HIE", "DEF", "HEF", "DWF",
"TRL", "OGR", "HFL", "GNM",
"IKS", "VAH", "FRG", "DRK"
};
const uint16 race_values[17] = {
0,
HUMAN, BARBARIAN, ERUDITE, WOOD_ELF,
HIGH_ELF, DARK_ELF, HALF_ELF, DWARF,
TROLL, OGRE, HALFLING, GNOME,
IKSAR, VAHSHIR, FROGLOK, DRAKKIN
RACE_DOUG_0,
RACE_HUMAN_1, RACE_BARBARIAN_2, RACE_ERUDITE_3, RACE_WOOD_ELF_4,
RACE_HIGH_ELF_5, RACE_DARK_ELF_6, RACE_HALF_ELF_7, RACE_DWARF_8,
RACE_TROLL_9, RACE_OGRE_10, RACE_HALFLING_11, RACE_GNOME_12,
RACE_IKSAR_128, RACE_VAH_SHIR_130, RACE_FROGLOK_330, RACE_DRAKKIN_522
};
const std::string gender_substrs[2] = {
"{} (M)", "{} (F)",
"Male", "Female",
};
if (helper_command_alias_fail(c, "bot_subcommand_bot_create", sep->arg[0], "botcreate")) {
@@ -5472,7 +5525,7 @@ void bot_subcommand_bot_create(Client *c, const Seperator *sep)
c->Message(
Chat::White,
fmt::format(
"Usage: {} [bot_name] [bot_class] [bot_race] [bot_gender]",
"Usage: {} [Name] [Class] [Race] [Gender]",
sep->arg[0]
).c_str()
);
@@ -5480,22 +5533,27 @@ void bot_subcommand_bot_create(Client *c, const Seperator *sep)
std::string window_text;
std::string message_separator;
int object_count = 0;
const int object_max = 5;
const int object_max = 4;
window_text.append("<c \"#FFFFFF\">Classes:<c \"#FFFF\">");
window_text.append(
fmt::format(
"Classes{}<c \"#FFFF\">",
DialogueWindow::Break()
)
);
message_separator = " ";
object_count = 1;
object_count = 0;
for (int i = 0; i <= 15; ++i) {
window_text.append(message_separator);
if (object_count >= object_max) {
window_text.append("<br>");
window_text.append(DialogueWindow::Break());
object_count = 0;
}
window_text.append(
fmt::format("{} {}",
fmt::format("{} ({})",
class_substrs[i + 1],
(i + 1)
)
@@ -5505,22 +5563,27 @@ void bot_subcommand_bot_create(Client *c, const Seperator *sep)
message_separator = ", ";
}
window_text.append("<br><br>");
window_text.append(DialogueWindow::Break(2));
window_text.append("<c \"#FFFFFF\">Races:<c \"#FFFF\">");
window_text.append(
fmt::format(
"<c \"#FFFFFF\">Races{}<c \"#FFFF\">",
DialogueWindow::Break()
)
);
message_separator = " ";
object_count = 1;
object_count = 0;
for (int i = 0; i <= 15; ++i) {
window_text.append(message_separator);
if (object_count >= object_max) {
window_text.append("<br>");
window_text.append(DialogueWindow::Break());
object_count = 0;
}
window_text.append(
fmt::format("{}, {}",
fmt::format("{} ({})",
race_substrs[i + 1],
race_values[i + 1]
)
@@ -5530,16 +5593,21 @@ void bot_subcommand_bot_create(Client *c, const Seperator *sep)
message_separator = ", ";
}
window_text.append("<br><br>");
window_text.append(DialogueWindow::Break(2));
window_text.append("<c \"#FFFFFF\">Genders:<c \"#FFFF\">");
window_text.append(
fmt::format(
"<c \"#FFFFFF\">Genders{}<c \"#FFFF\">",
DialogueWindow::Break()
)
);
message_separator = " ";
for (int i = 0; i <= 1; ++i) {
window_text.append(message_separator);
window_text.append(
fmt::format("{}, {}",
fmt::format("{} ({})",
gender_substrs[i],
i
)
@@ -5548,13 +5616,12 @@ void bot_subcommand_bot_create(Client *c, const Seperator *sep)
message_separator = ", ";
}
c->SendPopupToClient("Bot Create Options", window_text.c_str());
c->SendPopupToClient("Bot Creation Options", window_text.c_str());
return;
}
auto arguments = sep->argnum;
const auto arguments = sep->argnum;
if (!arguments || sep->IsNumber(1)) {
c->Message(Chat::White, "You must name your bot!");
return;
@@ -5581,15 +5648,18 @@ void bot_subcommand_bot_create(Client *c, const Seperator *sep)
return;
}
auto bot_gender = 0;
auto bot_gender = MALE;
if (sep->IsNumber(4)) {
bot_gender = static_cast<uint8>(std::stoul(sep->arg[4]));
if (bot_gender == NEUTER) {
bot_gender = MALE;
}
} else {
if (!strcasecmp(sep->arg[4], "m") || !strcasecmp(sep->arg[4], "male")) {
bot_gender = 0;
bot_gender = MALE;
} else if (!strcasecmp(sep->arg[4], "f") || !strcasecmp(sep->arg[4], "female")) {
bot_gender = 1;
bot_gender = FEMALE;
}
}
@@ -9407,6 +9477,14 @@ void bot_subcommand_inventory_remove(Client *c, const Seperator *sep)
slot_id
)
);
const auto export_string = fmt::format(
"{} {}",
inst->IsStackable() ? inst->GetCharges() : 1,
slot_id
);
parse->EventBot(EVENT_UNEQUIP_ITEM_BOT, my_bot, nullptr, export_string, inst->GetID());
}
}
+43 -4
View File
@@ -10446,8 +10446,8 @@ int Client::CountItem(uint32 item_id)
{ EQ::invslot::SHARED_BANK_BEGIN, EQ::invslot::SHARED_BANK_END },
{ EQ::invbag::SHARED_BANK_BAGS_BEGIN, EQ::invbag::SHARED_BANK_BAGS_END },
};
const size_t size = sizeof(slots) / sizeof(slots[0]);
for (int slot_index = 0; slot_index < size; ++slot_index) {
const size_t slot_index_count = sizeof(slots) / sizeof(slots[0]);
for (int slot_index = 0; slot_index < slot_index_count; ++slot_index) {
for (int slot_id = slots[slot_index][0]; slot_id <= slots[slot_index][1]; ++slot_id) {
item = GetInv().GetItem(slot_id);
if (item && item->GetID() == item_id) {
@@ -10459,6 +10459,45 @@ int Client::CountItem(uint32 item_id)
return quantity;
}
void Client::ResetItemCooldown(uint32 item_id)
{
EQ::ItemInstance *item = nullptr;
const EQ::ItemData* item_d = database.GetItem(item_id);
if (!item_d) {
return;
}
int recast_type = item_d->RecastType;
bool found_item = false;
static const int16 slots[][2] = {
{ EQ::invslot::POSSESSIONS_BEGIN, EQ::invslot::POSSESSIONS_END },
{ EQ::invbag::GENERAL_BAGS_BEGIN, EQ::invbag::GENERAL_BAGS_END },
{ EQ::invbag::CURSOR_BAG_BEGIN, EQ::invbag::CURSOR_BAG_END},
{ EQ::invslot::BANK_BEGIN, EQ::invslot::BANK_END },
{ EQ::invbag::BANK_BAGS_BEGIN, EQ::invbag::BANK_BAGS_END },
{ EQ::invslot::SHARED_BANK_BEGIN, EQ::invslot::SHARED_BANK_END },
{ EQ::invbag::SHARED_BANK_BAGS_BEGIN, EQ::invbag::SHARED_BANK_BAGS_END },
};
const size_t slot_index_count = sizeof(slots) / sizeof(slots[0]);
for (int slot_index = 0; slot_index < slot_index_count; ++slot_index) {
for (int slot_id = slots[slot_index][0]; slot_id <= slots[slot_index][1]; ++slot_id) {
item = GetInv().GetItem(slot_id);
if (item) {
item_d = item->GetItem();
if (item_d && item->GetID() == item_id || (item_d->RecastType != RECAST_TYPE_UNLINKED_ITEM && item_d->RecastType == recast_type)) {
item->SetRecastTimestamp(0);
DeleteItemRecastTimer(item_d->ID);
SendItemPacket(slot_id, item, ItemPacketCharmUpdate);
found_item = true;
}
}
}
}
if (!found_item) {
DeleteItemRecastTimer(item_id); //We didn't find the item but we still want to remove the timer
}
}
void Client::RemoveItem(uint32 item_id, uint32 quantity)
{
EQ::ItemInstance *item = nullptr;
@@ -10472,8 +10511,8 @@ void Client::RemoveItem(uint32 item_id, uint32 quantity)
{ EQ::invbag::SHARED_BANK_BAGS_BEGIN, EQ::invbag::SHARED_BANK_BAGS_END },
};
int16 removed_count = 0;
const size_t size = sizeof(slots) / sizeof(slots[0]);
for (int slot_index = 0; slot_index < size; ++slot_index) {
const size_t slot_index_count = sizeof(slots) / sizeof(slots[0]);
for (int slot_index = 0; slot_index < slot_index_count; ++slot_index) {
for (int slot_id = slots[slot_index][0]; slot_id <= slots[slot_index][1]; ++slot_id) {
if (removed_count == quantity) {
break;
+3 -1
View File
@@ -966,6 +966,7 @@ public:
void SendCursorBuffer();
void DeleteItemInInventory(int16 slot_id, int16 quantity = 0, bool client_update = false, bool update_db = true);
int CountItem(uint32 item_id);
void ResetItemCooldown(uint32 item_id);
void RemoveItem(uint32 item_id, uint32 quantity = 1);
bool SwapItem(MoveItem_Struct* move_in);
void SwapItemResync(MoveItem_Struct* move_slots);
@@ -1516,8 +1517,9 @@ public:
void SendReloadCommandMessages();
void SendItemRecastTimer(int32 recast_type, uint32 recast_delay = 0);
void SendItemRecastTimer(int32 recast_type, uint32 recast_delay = 0, bool in_ignore_casting_requirement = false);
void SetItemRecastTimer(int32 spell_id, uint32 inventory_slot);
void DeleteItemRecastTimer(uint32 item_id);
bool HasItemRecastTimer(int32 spell_id, uint32 inventory_slot);
inline bool AggroMeterAvailable() const { return ((m_ClientVersionBit & EQ::versions::maskRoF2AndLater)) && RuleB(Character, EnableAggroMeter); } // RoF untested
+11 -3
View File
@@ -8927,9 +8927,12 @@ void Client::Handle_OP_ItemVerifyRequest(const EQApplicationPacket *app)
{
if (item->RecastDelay > 0)
{
if (!GetPTimers().Expired(&database, (pTimerItemStart + item->RecastType), false)) {
if (item->RecastType != RECAST_TYPE_UNLINKED_ITEM && !GetPTimers().Expired(&database, (pTimerItemStart + item->RecastType), false)) {
SendItemRecastTimer(item->RecastType); //Problem: When you loot corpse, recast display is not present. This causes it to display again. Could not get to display when sending from looting.
MessageString(Chat::Red, SPELL_RECAST);
SendSpellBarEnable(item->Click.Effect);
LogSpells("Casting of [{}] canceled: item spell reuse timer not expired", spell_id);
return;
} else if (item->RecastType == RECAST_TYPE_UNLINKED_ITEM && !GetPTimers().Expired(&database, (pTimerNegativeItemReuse * item->ID), false)) {
SendSpellBarEnable(item->Click.Effect);
LogSpells("Casting of [{}] canceled: item spell reuse timer not expired", spell_id);
return;
@@ -8972,11 +8975,16 @@ void Client::Handle_OP_ItemVerifyRequest(const EQApplicationPacket *app)
{
if (augitem->RecastDelay > 0)
{
if (!GetPTimers().Expired(&database, (pTimerItemStart + augitem->RecastType), false)) {
if (augitem->RecastType != RECAST_TYPE_UNLINKED_ITEM && !GetPTimers().Expired(&database, (pTimerItemStart + augitem->RecastType), false)) {
LogSpells("Casting of [{}] canceled: item spell reuse timer from augment not expired", spell_id);
MessageString(Chat::Red, SPELL_RECAST);
SendSpellBarEnable(augitem->Click.Effect);
return;
} else if (augitem->RecastType == RECAST_TYPE_UNLINKED_ITEM && !GetPTimers().Expired(&database, (pTimerNegativeItemReuse * augitem->ID), false)) {
MessageString(Chat::Red, SPELL_RECAST);
SendSpellBarEnable(augitem->Click.Effect);
LogSpells("Casting of [{}] canceled: item spell reuse timer not expired", spell_id);
return;
}
}
+24 -4
View File
@@ -1209,8 +1209,13 @@ void Corpse::MakeLootRequestPackets(Client* client, const EQApplicationPacket* a
auto pkinst = database.CreateItem(pkitem, pkitem->MaxCharges);
if (pkinst) {
if (pkitem->RecastDelay)
pkinst->SetRecastTimestamp(timestamps.count(pkitem->RecastType) ? timestamps.at(pkitem->RecastType) : 0);
if (pkitem->RecastDelay) {
if (pkitem->RecastType != RECAST_TYPE_UNLINKED_ITEM) {
pkinst->SetRecastTimestamp(timestamps.count(pkitem->RecastType) ? timestamps.at(pkitem->RecastType) : 0);
} else {
pkinst->SetRecastTimestamp(timestamps.count(pkitem->ID) ? timestamps.at(pkitem->ID) : 0);
}
}
LogInventory("MakeLootRequestPackets() Slot [{}], Item [{}]", EQ::invslot::CORPSE_BEGIN, pkitem->Name);
@@ -1264,8 +1269,13 @@ void Corpse::MakeLootRequestPackets(Client* client, const EQApplicationPacket* a
if (!inst)
continue;
if (item->RecastDelay)
inst->SetRecastTimestamp(timestamps.count(item->RecastType) ? timestamps.at(item->RecastType) : 0);
if (item->RecastDelay) {
if (item->RecastType != RECAST_TYPE_UNLINKED_ITEM) {
inst->SetRecastTimestamp(timestamps.count(item->RecastType) ? timestamps.at(item->RecastType) : 0);
} else {
inst->SetRecastTimestamp(timestamps.count(item->ID) ? timestamps.at(item->ID) : 0);
}
}
LogInventory("MakeLootRequestPackets() Slot [{}], Item [{}]", loot_slot, item->Name);
@@ -1477,6 +1487,16 @@ void Corpse::LootItem(Client *client, const EQApplicationPacket *app)
// get count for task update before it's mutated by AutoPutLootInInventory
int count = inst->IsStackable() ? inst->GetCharges() : 1;
//Set recast on item when looting it!
auto timestamps = database.GetItemRecastTimestamps(client->CharacterID());
const auto* d = inst->GetItem();
if (d->RecastDelay) {
if (d->RecastType != RECAST_TYPE_UNLINKED_ITEM) {
inst->SetRecastTimestamp(timestamps.count(d->RecastType) ? timestamps.at(d->RecastType) : 0);
} else {
inst->SetRecastTimestamp(timestamps.count(d->ID) ? timestamps.at(d->ID) : 0);
}
}
/* First add it to the looter - this will do the bag contents too */
if (lootitem->auto_loot > 0) {
+11
View File
@@ -169,6 +169,8 @@ const char *QuestEventSubroutines[_LargestEventID] = {
"EVENT_BOT_CREATE",
"EVENT_AUGMENT_INSERT_CLIENT",
"EVENT_AUGMENT_REMOVE_CLIENT",
"EVENT_EQUIP_ITEM_BOT",
"EVENT_UNEQUIP_ITEM_BOT",
// Add new events before these or Lua crashes
"EVENT_SPELL_EFFECT_BOT",
"EVENT_SPELL_EFFECT_BUFF_TIC_BOT"
@@ -1901,6 +1903,15 @@ void PerlembParser::ExportEventVariables(
break;
}
case EVENT_EQUIP_ITEM_BOT:
case EVENT_UNEQUIP_ITEM_BOT: {
Seperator sep(data);
ExportVar(package_name.c_str(), "item_id", extradata);
ExportVar(package_name.c_str(), "item_quantity", sep.arg[0]);
ExportVar(package_name.c_str(), "slot_id", sep.arg[1]);
break;
}
case EVENT_AUGMENT_INSERT_CLIENT:
case EVENT_AUGMENT_REMOVE_CLIENT: {
Seperator sep(data);
+2
View File
@@ -114,6 +114,8 @@ typedef enum {
EVENT_BOT_CREATE,
EVENT_AUGMENT_INSERT_CLIENT,
EVENT_AUGMENT_REMOVE_CLIENT,
EVENT_EQUIP_ITEM_BOT,
EVENT_UNEQUIP_ITEM_BOT,
// Add new events before these or Lua crashes
EVENT_SPELL_EFFECT_BOT,
EVENT_SPELL_EFFECT_BUFF_TIC_BOT,
+9
View File
@@ -714,6 +714,15 @@ bool Client::SummonItem(uint32 item_id, int16 charges, uint32 aug1, uint32 aug2,
// in any other situation just use charges as passed
EQ::ItemInstance* inst = database.CreateItem(item, charges);
auto timestamps = database.GetItemRecastTimestamps(CharacterID());
const auto* d = inst->GetItem();
if (d->RecastDelay) {
if (d->RecastType != RECAST_TYPE_UNLINKED_ITEM) {
inst->SetRecastTimestamp(timestamps.count(d->RecastType) ? timestamps.at(d->RecastType) : 0);
} else {
inst->SetRecastTimestamp(timestamps.count(d->ID) ? timestamps.at(d->ID) : 0);
}
}
if(inst == nullptr) {
Message(Chat::Red, "An unknown server error has occurred and your item was not created.");
+7
View File
@@ -3014,6 +3014,12 @@ void Lua_Client::CampAllBots(uint8 class_id)
self->CampAllBots(class_id);
}
void Lua_Client::ResetItemCooldown(uint32 item_id)
{
Lua_Safe_Call_Void();
self->ResetItemCooldown(item_id);
}
luabind::scope lua_register_client() {
return luabind::class_<Lua_Client, Lua_Mob>("Client")
.def(luabind::constructor<>())
@@ -3373,6 +3379,7 @@ luabind::scope lua_register_client() {
.def("ResetCastbarCooldownBySlot", (void(Lua_Client::*)(int))&Lua_Client::ResetCastbarCooldownBySlot)
.def("ResetCastbarCooldownBySpellID", (void(Lua_Client::*)(uint32))&Lua_Client::ResetCastbarCooldownBySpellID)
.def("ResetDisciplineTimer", (void(Lua_Client::*)(uint32))&Lua_Client::ResetDisciplineTimer)
.def("ResetItemCooldown", (void(Lua_Client::*)(uint32))&Lua_Client::ResetItemCooldown)
.def("ResetTrade", (void(Lua_Client::*)(void))&Lua_Client::ResetTrade)
.def("RewardFaction", (void(Lua_Client::*)(int,int))&Lua_Client::RewardFaction)
.def("Save", (void(Lua_Client::*)(int))&Lua_Client::Save)
+1
View File
@@ -462,6 +462,7 @@ public:
bool CanEnterZone(std::string zone_short_name);
bool CanEnterZone(std::string zone_short_name, int16 instance_version);
void SendPath(Lua_Mob target);
void ResetItemCooldown(uint32 item_id);
void ApplySpell(int spell_id);
void ApplySpell(int spell_id, int duration);
+3 -1
View File
@@ -4625,7 +4625,9 @@ luabind::scope lua_register_events() {
luabind::value("despawn_zone", static_cast<int>(EVENT_DESPAWN_ZONE)),
luabind::value("bot_create", static_cast<int>(EVENT_BOT_CREATE)),
luabind::value("augment_insert_client", static_cast<int>(EVENT_AUGMENT_INSERT_CLIENT)),
luabind::value("augment_remove_client", static_cast<int>(EVENT_AUGMENT_REMOVE_CLIENT))
luabind::value("augment_remove_client", static_cast<int>(EVENT_AUGMENT_REMOVE_CLIENT)),
luabind::value("equip_item_bot", static_cast<int>(EVENT_EQUIP_ITEM_BOT)),
luabind::value("unequip_item_bot", static_cast<int>(EVENT_UNEQUIP_ITEM_BOT))
)];
}
+4
View File
@@ -156,6 +156,8 @@ const char *LuaEvents[_LargestEventID] = {
"event_bot_create",
"event_augment_insert_client",
"event_augment_remove_client",
"event_equip_item_bot",
"event_unequip_item_bot",
};
extern Zone *zone;
@@ -308,6 +310,8 @@ LuaParser::LuaParser() {
BotArgumentDispatch[EVENT_TRADE] = handle_bot_trade;
BotArgumentDispatch[EVENT_USE_SKILL] = handle_bot_use_skill;
BotArgumentDispatch[EVENT_PAYLOAD] = handle_bot_payload;
BotArgumentDispatch[EVENT_EQUIP_ITEM_BOT] = handle_bot_equip_item;
BotArgumentDispatch[EVENT_UNEQUIP_ITEM_BOT] = handle_bot_equip_item;
#endif
L = nullptr;
+53
View File
@@ -1951,4 +1951,57 @@ void handle_bot_use_skill(
lua_setfield(L, -2, "skill_level");
}
void handle_bot_equip_item(
QuestInterface *parse,
lua_State* L,
Bot* bot,
Mob* init,
std::string data,
uint32 extra_data,
std::vector<std::any> *extra_pointers
) {
lua_pushnumber(L, extra_data);
lua_setfield(L, -2, "item_id");
Seperator sep(data.c_str());
lua_pushnumber(L, std::stoi(sep.arg[0]));
lua_setfield(L, -2, "item_quantity");
lua_pushnumber(L, std::stoi(sep.arg[1]));
lua_setfield(L, -2, "slot_id");
Lua_ItemInst l_item(extra_data);
luabind::adl::object l_item_o = luabind::adl::object(L, l_item);
l_item_o.push(L);
lua_setfield(L, -2, "item");
}
void handle_bot_unequip_item(
QuestInterface *parse,
lua_State* L,
Bot* bot,
Mob* init,
std::string data,
uint32 extra_data,
std::vector<std::any> *extra_pointers
) {
lua_pushnumber(L, extra_data);
lua_setfield(L, -2, "item_id");
Seperator sep(data.c_str());
lua_pushnumber(L, std::stoi(sep.arg[0]));
lua_setfield(L, -2, "item_quantity");
lua_pushnumber(L, std::stoi(sep.arg[1]));
lua_setfield(L, -2, "slot_id");
Lua_ItemInst l_item(extra_data);
luabind::adl::object l_item_o = luabind::adl::object(L, l_item);
l_item_o.push(L);
lua_setfield(L, -2, "item");
}
#endif
+20
View File
@@ -955,5 +955,25 @@ void handle_bot_payload(
std::vector<std::any> *extra_pointers
);
void handle_bot_equip_item(
QuestInterface *parse,
lua_State* L,
Bot* bot,
Mob* init,
std::string data,
uint32 extra_data,
std::vector<std::any> *extra_pointers
);
void handle_bot_unequip_item(
QuestInterface *parse,
lua_State* L,
Bot* bot,
Mob* init,
std::string data,
uint32 extra_data,
std::vector<std::any> *extra_pointers
);
#endif
#endif
+9 -3
View File
@@ -507,9 +507,15 @@ bool Object::HandleClick(Client* sender, const ClickObject_Struct* click_object)
cursordelete = true; // otherwise, we delete the new one
}
if (item->RecastDelay)
m_inst->SetRecastTimestamp(
database.GetItemRecastTimestamp(sender->CharacterID(), item->RecastType));
if (item->RecastDelay) {
if (item->RecastType != RECAST_TYPE_UNLINKED_ITEM) {
m_inst->SetRecastTimestamp(
database.GetItemRecastTimestamp(sender->CharacterID(), item->RecastType));
} else {
m_inst->SetRecastTimestamp(
database.GetItemRecastTimestamp(sender->CharacterID(), item->ID));
}
}
std::string export_string = fmt::format("{}", item->ID);
std::vector<std::any> args;
+6
View File
@@ -2872,6 +2872,11 @@ void Perl_Client_CampAllBots(Client* self, uint8 class_id)
self->CampAllBots(class_id);
}
void Perl_Client_ResetItemCooldown(Client* self, uint32 item_id)
{
self->ResetItemCooldown(item_id);
}
void perl_register_client()
{
perl::interpreter perl(PERL_GET_THX);
@@ -3231,6 +3236,7 @@ void perl_register_client()
package.add("ResetCastbarCooldownBySlot", &Perl_Client_ResetCastbarCooldownBySlot);
package.add("ResetCastbarCooldownBySpellID", &Perl_Client_ResetCastbarCooldownBySpellID);
package.add("ResetDisciplineTimer", &Perl_Client_ResetDisciplineTimer);
package.add("ResetItemCooldown", &Perl_Client_ResetItemCooldown);
package.add("ResetTrade", &Perl_Client_ResetTrade);
package.add("Save", &Perl_Client_Save);
package.add("SaveBackup", &Perl_Client_SaveBackup);
+42 -7
View File
@@ -6342,9 +6342,9 @@ void Client::SendSpellAnim(uint16 target_id, uint16 spell_id)
entity_list.QueueCloseClients(this, &app, false, RuleI(Range, SpellParticles));
}
void Client::SendItemRecastTimer(int32 recast_type, uint32 recast_delay)
void Client::SendItemRecastTimer(int32 recast_type, uint32 recast_delay, bool in_ignore_casting_requirement)
{
if (recast_type == -1) {
if (recast_type == RECAST_TYPE_UNLINKED_ITEM) {
return;
}
@@ -6357,6 +6357,7 @@ void Client::SendItemRecastTimer(int32 recast_type, uint32 recast_delay)
ItemRecastDelay_Struct *ird = (ItemRecastDelay_Struct *)outapp->pBuffer;
ird->recast_delay = recast_delay;
ird->recast_type = static_cast<uint32>(recast_type);
ird->ignore_casting_requirement = in_ignore_casting_requirement; //True allows reset of item cast timers
QueuePacket(outapp);
safe_delete(outapp);
}
@@ -6369,10 +6370,12 @@ void Client::SetItemRecastTimer(int32 spell_id, uint32 inventory_slot)
int recast_delay = 0;
int recast_type = 0;
bool from_augment = false;
int item_casting = 0;
if (!item) {
return;
}
item_casting = item->GetItem()->ID;
//Check primary item.
if (item->GetItem()->RecastDelay > 0) {
@@ -6396,6 +6399,7 @@ void Client::SetItemRecastTimer(int32 spell_id, uint32 inventory_slot)
recast_delay = aug_i->GetItem()->RecastDelay;
recast_type = aug_i->GetItem()->RecastType;
from_augment = true;
item_casting = aug_i->GetItem()->ID;
break;
}
}
@@ -6409,14 +6413,21 @@ void Client::SetItemRecastTimer(int32 spell_id, uint32 inventory_slot)
recast_delay = std::max(recast_delay, 0);
if (recast_delay > 0) {
GetPTimers().Start((pTimerItemStart + recast_type), static_cast<uint32>(recast_delay));
if (recast_type != -1) {
database.UpdateItemRecastTimestamps(
if (recast_type != RECAST_TYPE_UNLINKED_ITEM) {
GetPTimers().Start((pTimerItemStart + recast_type), static_cast<uint32>(recast_delay));
database.UpdateItemRecast(
CharacterID(),
recast_type,
GetPTimers().Get(pTimerItemStart + recast_type)->GetReadyTimestamp()
);
} else if (recast_type == RECAST_TYPE_UNLINKED_ITEM) {
GetPTimers().Start((pTimerNegativeItemReuse * item_casting), static_cast<uint32>(recast_delay));
database.UpdateItemRecast(
CharacterID(),
item_casting,
GetPTimers().Get(pTimerNegativeItemReuse * item_casting)->GetReadyTimestamp()
);
}
if (!from_augment) {
@@ -6425,12 +6436,32 @@ void Client::SetItemRecastTimer(int32 spell_id, uint32 inventory_slot)
}
}
void Client::DeleteItemRecastTimer(uint32 item_id)
{
const auto* d = database.GetItem(item_id);
if (!d) {
return;
}
const auto recast_type = d->RecastType != RECAST_TYPE_UNLINKED_ITEM ? d->RecastType : item_id;
const int timer_id = d->RecastType != RECAST_TYPE_UNLINKED_ITEM ? (pTimerItemStart + recast_type) : (pTimerNegativeItemReuse * item_id);
database.DeleteItemRecast(CharacterID(), recast_type);
GetPTimers().Clear(&database, timer_id);
if (recast_type != RECAST_TYPE_UNLINKED_ITEM) {
SendItemRecastTimer(recast_type, 1, true);
}
}
bool Client::HasItemRecastTimer(int32 spell_id, uint32 inventory_slot)
{
EQ::ItemInstance *item = CastToClient()->GetInv().GetItem(inventory_slot);
int recast_delay = 0;
int recast_type = 0;
int item_id = 0;
bool from_augment = false;
if (!item) {
@@ -6445,6 +6476,7 @@ bool Client::HasItemRecastTimer(int32 spell_id, uint32 inventory_slot)
if (item->GetItem()->RecastDelay > 0) {
recast_type = item->GetItem()->RecastType;
recast_delay = item->GetItem()->RecastDelay;
item_id = item->GetItem()->ID;
}
//Check augmenent
else {
@@ -6463,6 +6495,7 @@ bool Client::HasItemRecastTimer(int32 spell_id, uint32 inventory_slot)
if (aug_i->GetItem() && aug_i->GetItem()->RecastDelay > 0) {
recast_delay = aug_i->GetItem()->RecastDelay;
recast_type = aug_i->GetItem()->RecastType;
item_id = aug_i->GetItem()->ID;
}
break;
}
@@ -6473,7 +6506,9 @@ bool Client::HasItemRecastTimer(int32 spell_id, uint32 inventory_slot)
return false;
}
//if time is not expired, then it exists and therefore we have a recast on this item.
if (!CastToClient()->GetPTimers().Expired(&database, (pTimerItemStart + recast_type), false)) {
if (recast_type != RECAST_TYPE_UNLINKED_ITEM && !CastToClient()->GetPTimers().Expired(&database, (pTimerItemStart + recast_type), false)) {
return true;
} else if (recast_type == RECAST_TYPE_UNLINKED_ITEM && !CastToClient()->GetPTimers().Expired(&database, (pTimerNegativeItemReuse * item_id), false)) {
return true;
}
+17 -4
View File
@@ -3333,11 +3333,24 @@ void ZoneDatabase::RemoveTempFactions(Client *client) {
QueryDatabase(query);
}
void ZoneDatabase::UpdateItemRecastTimestamps(uint32 char_id, uint32 recast_type, uint32 timestamp)
void ZoneDatabase::UpdateItemRecast(uint32 character_id, uint32 recast_type, uint32 timestamp)
{
std::string query =
StringFormat("REPLACE INTO character_item_recast (id, recast_type, timestamp) VALUES (%u, %u, %u)", char_id,
recast_type, timestamp);
const auto query = fmt::format(
"REPLACE INTO character_item_recast (id, recast_type, timestamp) VALUES ({}, {}, {})",
character_id,
recast_type,
timestamp
);
QueryDatabase(query);
}
void ZoneDatabase::DeleteItemRecast(uint32 character_id, uint32 recast_type)
{
const auto query = fmt::format(
"DELETE FROM character_item_recast WHERE id = {} AND recast_type = {}",
character_id,
recast_type
);
QueryDatabase(query);
}
+2 -1
View File
@@ -369,7 +369,8 @@ public:
void LoadPetInfo(Client *c);
void SavePetInfo(Client *c);
void RemoveTempFactions(Client *c);
void UpdateItemRecastTimestamps(uint32 char_id, uint32 recast_type, uint32 timestamp);
void UpdateItemRecast(uint32 char_id, uint32 recast_type, uint32 timestamp);
void DeleteItemRecast(uint32 char_id, uint32 recast_type);
bool DeleteCharacterAAs(uint32 character_id);
bool DeleteCharacterBandolier(uint32 character_id, uint32 band_id);