Added bot commands 'applypoison' and 'applypotion' .. new bot owner option 'buffcounter'

This commit is contained in:
Uleat 2019-12-03 22:01:13 -05:00
parent 86593798a9
commit 35fe27eb5d
9 changed files with 433 additions and 4 deletions

View File

@ -599,6 +599,9 @@ RULE_INT(Bots, AllowedGenders, 0x3, "Bitmask of allowed bot genders")
RULE_BOOL(Bots, AllowOwnerOptionAltCombat, true, "When option is enabled, bots will use an auto-/shared-aggro combat model")
RULE_BOOL(Bots, AllowOwnerOptionAutoDefend, true, "When option is enabled, bots will defend their owner on enemy aggro")
RULE_REAL(Bots, LeashDistance, 562500.0f, "Distance a bot is allowed to travel from leash owner before being pulled back (squared value)")
RULE_BOOL(Bots, AllowApplyPoisonCommand, true, "Allows the use of the bot command 'applypoison'")
RULE_BOOL(Bots, AllowApplyPotionCommand, true, "Allows the use of the bot command 'applypotion'")
RULE_BOOL(Bots, RestrictApplyPotionToRogue, true, "Restricts the bot command 'applypotion' to rogue-usable potions (i.e., poisons)")
RULE_CATEGORY_END()
#endif

View File

@ -236,8 +236,157 @@ Bot::Bot(uint32 botID, uint32 botOwnerCharacterID, uint32 botSpellsID, double to
LoadAAs();
if (!database.botdb.LoadBuffs(this) && bot_owner)
// copied from client CompleteConnect() handler - watch for problems
// (may have to move to post-spawn location if certain buffs still don't process correctly)
if (database.botdb.LoadBuffs(this) && bot_owner) {
//reapply some buffs
uint32 buff_count = GetMaxTotalSlots();
for (uint32 j1 = 0; j1 < buff_count; j1++) {
if (!IsValidSpell(buffs[j1].spellid))
continue;
const SPDat_Spell_Struct& spell = spells[buffs[j1].spellid];
int NimbusEffect = GetNimbusEffect(buffs[j1].spellid);
if (NimbusEffect) {
if (!IsNimbusEffectActive(NimbusEffect))
SendSpellEffect(NimbusEffect, 500, 0, 1, 3000, true);
}
for (int x1 = 0; x1 < EFFECT_COUNT; x1++) {
switch (spell.effectid[x1]) {
case SE_IllusionCopy:
case SE_Illusion: {
if (spell.base[x1] == -1) {
if (gender == 1)
gender = 0;
else if (gender == 0)
gender = 1;
SendIllusionPacket(GetRace(), gender, 0xFF, 0xFF);
}
else if (spell.base[x1] == -2) // WTF IS THIS
{
if (GetRace() == 128 || GetRace() == 130 || GetRace() <= 12)
SendIllusionPacket(GetRace(), GetGender(), spell.base2[x1], spell.max[x1]);
}
else if (spell.max[x1] > 0)
{
SendIllusionPacket(spell.base[x1], 0xFF, spell.base2[x1], spell.max[x1]);
}
else
{
SendIllusionPacket(spell.base[x1], 0xFF, 0xFF, 0xFF);
}
switch (spell.base[x1]) {
case OGRE:
SendAppearancePacket(AT_Size, 9);
break;
case TROLL:
SendAppearancePacket(AT_Size, 8);
break;
case VAHSHIR:
case BARBARIAN:
SendAppearancePacket(AT_Size, 7);
break;
case HALF_ELF:
case WOOD_ELF:
case DARK_ELF:
case FROGLOK:
SendAppearancePacket(AT_Size, 5);
break;
case DWARF:
SendAppearancePacket(AT_Size, 4);
break;
case HALFLING:
case GNOME:
SendAppearancePacket(AT_Size, 3);
break;
default:
SendAppearancePacket(AT_Size, 6);
break;
}
break;
}
//case SE_SummonHorse: {
// SummonHorse(buffs[j1].spellid);
// //hasmount = true; //this was false, is that the correct thing?
// break;
//}
case SE_Silence:
{
Silence(true);
break;
}
case SE_Amnesia:
{
Amnesia(true);
break;
}
case SE_DivineAura:
{
invulnerable = true;
break;
}
case SE_Invisibility2:
case SE_Invisibility:
{
invisible = true;
SendAppearancePacket(AT_Invis, 1);
break;
}
case SE_Levitate:
{
if (!zone->CanLevitate())
{
//if (!GetGM())
//{
SendAppearancePacket(AT_Levitate, 0);
BuffFadeByEffect(SE_Levitate);
//Message(Chat::Red, "You can't levitate in this zone.");
//}
}
else {
SendAppearancePacket(AT_Levitate, 2);
}
break;
}
case SE_InvisVsUndead2:
case SE_InvisVsUndead:
{
invisible_undead = true;
break;
}
case SE_InvisVsAnimals:
{
invisible_animals = true;
break;
}
case SE_AddMeleeProc:
case SE_WeaponProc:
{
AddProcToWeapon(GetProcID(buffs[j1].spellid, x1), false, 100 + spells[buffs[j1].spellid].base2[x1], buffs[j1].spellid, buffs[j1].casterlevel);
break;
}
case SE_DefensiveProc:
{
AddDefensiveProc(GetProcID(buffs[j1].spellid, x1), 100 + spells[buffs[j1].spellid].base2[x1], buffs[j1].spellid);
break;
}
case SE_RangedProc:
{
AddRangedProc(GetProcID(buffs[j1].spellid, x1), 100 + spells[buffs[j1].spellid].base2[x1], buffs[j1].spellid);
break;
}
}
}
}
}
else {
bot_owner->Message(Chat::Red, "&s for '%s'", BotDatabase::fail::LoadBuffs(), GetCleanName());
}
CalcBotStats(false);
hp_regen = CalcHPRegen();
@ -4726,9 +4875,9 @@ bool Bot::Death(Mob *killerMob, int32 damage, uint16 spell_id, EQEmu::skills::Sk
Mob *my_owner = GetBotOwner();
if (my_owner && my_owner->IsClient() && my_owner->CastToClient()->GetBotOption(Client::booDeathMarquee)) {
if (killerMob)
my_owner->CastToClient()->SendMarqueeMessage(Chat::Yellow, 510, 0, 1000, 3000, StringFormat("%s has been slain by %s", GetCleanName(), killerMob->GetCleanName()));
my_owner->CastToClient()->SendMarqueeMessage(Chat::Red, 510, 0, 1000, 3000, StringFormat("%s has been slain by %s", GetCleanName(), killerMob->GetCleanName()));
else
my_owner->CastToClient()->SendMarqueeMessage(Chat::Yellow, 510, 0, 1000, 3000, StringFormat("%s has been slain", GetCleanName()));
my_owner->CastToClient()->SendMarqueeMessage(Chat::Red, 510, 0, 1000, 3000, StringFormat("%s has been slain", GetCleanName()));
}
Mob *give_exp = hate_list.GetDamageTopOnHateList(this);
@ -8989,6 +9138,20 @@ Bot* EntityList::GetBotByBotName(std::string botName) {
return Result;
}
Client* EntityList::GetBotOwnerByBotEntityID(uint16 entityID) {
Client* Result = nullptr;
if (entityID > 0) {
for (std::list<Bot*>::iterator botListItr = bot_list.begin(); botListItr != bot_list.end(); ++botListItr) {
Bot* tempBot = *botListItr;
if (tempBot && tempBot->GetID() == entityID) {
Result = tempBot->GetBotOwner()->CastToClient();
break;
}
}
}
return Result;
}
void EntityList::AddBot(Bot *newBot, bool SendSpawnPacket, bool dontqueue) {
if(newBot) {
newBot->SetID(GetFreeID());

View File

@ -1321,6 +1321,8 @@ int bot_command_init(void)
if (
bot_command_add("actionable", "Lists actionable command arguments and use descriptions", 0, bot_command_actionable) ||
bot_command_add("aggressive", "Orders a bot to use a aggressive discipline", 0, bot_command_aggressive) ||
bot_command_add("applypoison", "Applies cursor-held poison to a rogue bot's weapon", 0, bot_command_apply_poison) ||
bot_command_add("applypotion", "Applies cursor-held potion to a bot's effects", 0, bot_command_apply_potion) ||
bot_command_add("attack", "Orders bots to attack a designated target", 0, bot_command_attack) ||
bot_command_add("bindaffinity", "Orders a bot to attempt an affinity binding", 0, bot_command_bind_affinity) ||
bot_command_add("bot", "Lists the available bot management [subcommands]", 0, bot_command_bot) ||
@ -2579,6 +2581,166 @@ void bot_command_aggressive(Client *c, const Seperator *sep)
c->Message(m_action, "%i of %i bots have used aggressive disciplines", success_count, candidate_count);
}
void bot_command_apply_poison(Client *c, const Seperator *sep)
{
if (helper_command_disabled(c, RuleB(Bots, AllowApplyPoisonCommand), "applypoison")) {
return;
}
if (helper_command_alias_fail(c, "bot_command_apply_poison", sep->arg[0], "applypoison")) {
return;
}
if (helper_is_help_or_usage(sep->arg[1])) {
c->Message(m_usage, "usage: <rogue_bot_target> %s", sep->arg[0]);
return;
}
Bot *my_rogue_bot = nullptr;
if (c->GetTarget() && c->GetTarget()->IsBot() && c->GetTarget()->CastToBot()->GetBotOwnerCharacterID() == c->CharacterID() && c->GetTarget()->CastToBot()->GetClass() == ROGUE) {
my_rogue_bot = c->GetTarget()->CastToBot();
}
if (!my_rogue_bot) {
c->Message(m_fail, "You must target a rogue bot that you own to use this command!");
return;
}
if (my_rogue_bot->GetLevel() < 18) {
c->Message(m_fail, "Your rogue bot must be level 18 before %s can apply poison!", (my_rogue_bot->GetGender() == 1 ? "she" : "he"));
return;
}
const auto poison_instance = c->GetInv().GetItem(EQEmu::invslot::slotCursor);
if (!poison_instance) {
c->Message(m_fail, "No item found on cursor!");
return;
}
auto poison_data = poison_instance->GetItem();
if (!poison_data) {
c->Message(m_fail, "No data found for cursor item!");
return;
}
if (poison_data->ItemType == EQEmu::item::ItemTypePoison) {
if ((~poison_data->Races) & GetPlayerRaceBit(my_rogue_bot->GetRace())) {
c->Message(m_fail, "Invalid race for weapon poison!");
return;
}
if (poison_data->Proc.Level2 > my_rogue_bot->GetLevel()) {
c->Message(m_fail, "This poison is too powerful for your intended target!");
return;
}
// generalized from client ApplyPoison handler
double ChanceRoll = zone->random.Real(0, 1);
uint16 poison_skill = 95 + ((my_rogue_bot->GetLevel() - 18) * 5);
if (poison_skill > 200) {
poison_skill = 200;
}
bool apply_poison_chance = (ChanceRoll < (.75 + poison_skill / 1000));
if (apply_poison_chance && my_rogue_bot->AddProcToWeapon(poison_data->Proc.Effect, false, (my_rogue_bot->GetDEX() / 100) + 103, POISON_PROC)) {
c->Message(m_action, "Successfully applied %s to %s's weapon.", poison_data->Name, my_rogue_bot->GetCleanName());
}
else {
c->Message(m_fail, "Failed to apply %s to %s's weapon.", poison_data->Name, my_rogue_bot->GetCleanName());
}
c->DeleteItemInInventory(EQEmu::invslot::slotCursor, 1, true);
}
else {
c->Message(m_fail, "Item on cursor is not a weapon poison!");
return;
}
}
void bot_command_apply_potion(Client* c, const Seperator* sep)
{
if (helper_command_disabled(c, RuleB(Bots, AllowApplyPotionCommand), "applypotion")) {
return;
}
if (helper_command_alias_fail(c, "bot_command_apply_potion", sep->arg[0], "applypotion")) {
return;
}
if (helper_is_help_or_usage(sep->arg[1])) {
c->Message(m_usage, "usage: <bot_target> %s", sep->arg[0]);
return;
}
Bot* my_bot = nullptr;
if (c->GetTarget() && c->GetTarget()->IsBot() && c->GetTarget()->CastToBot()->GetBotOwnerCharacterID() == c->CharacterID()) {
my_bot = c->GetTarget()->CastToBot();
}
if (!my_bot) {
c->Message(m_fail, "You must target a bot that you own to use this command!");
return;
}
const auto potion_instance = c->GetInv().GetItem(EQEmu::invslot::slotCursor);
if (!potion_instance) {
c->Message(m_fail, "No item found on cursor!");
return;
}
auto potion_data = potion_instance->GetItem();
if (!potion_data) {
c->Message(m_fail, "No data found for cursor item!");
return;
}
if (potion_data->ItemType == EQEmu::item::ItemTypePotion && potion_data->Click.Effect > 0) {
if (RuleB(Bots, RestrictApplyPotionToRogue) && potion_data->Classes != PLAYER_CLASS_ROGUE_BIT) {
c->Message(m_fail, "This command is restricted to rogue poison potions only!");
return;
}
if ((~potion_data->Races) & GetPlayerRaceBit(my_bot->GetRace())) {
c->Message(m_fail, "Invalid race for potion!");
return;
}
if ((~potion_data->Classes) & GetPlayerClassBit(my_bot->GetClass())) {
c->Message(m_fail, "Invalid class for potion!");
return;
}
if (potion_data->Click.Level2 > my_bot->GetLevel()) {
c->Message(m_fail, "This potion is too powerful for your intended target!");
return;
}
// TODO: figure out best way to handle casting time/animation
if (my_bot->SpellFinished(potion_data->Click.Effect, my_bot, EQEmu::spells::CastingSlot::Item, 0)) {
c->Message(m_action, "Successfully applied %s to %s's buff effects.", potion_data->Name, my_bot->GetCleanName());
}
else {
c->Message(m_fail, "Failed to apply %s to %s's buff effects.", potion_data->Name, my_bot->GetCleanName());
}
c->DeleteItemInInventory(EQEmu::invslot::slotCursor, 1, true);
}
else {
c->Message(m_fail, "Item on cursor is not a potion!");
return;
}
}
void bot_command_attack(Client *c, const Seperator *sep)
{
if (helper_command_alias_fail(c, "bot_command_attack", sep->arg[0], "attack")) {
@ -3637,6 +3799,16 @@ void bot_command_owner_option(Client *c, const Seperator *sep)
"<td><c \"#00CCCC\">null</td>"
"<td><c \"#888888\">(toggles)</td>"
"</tr>"
"<tr>"
"<td><c \"#CCCCCC\">buffcounter</td>"
"<td><c \"#00CC00\">enable <c \"#CCCCCC\">| <c \"#00CC00\">disable</td>"
"<td><c \"#888888\">marquee message on buff counter change</td>"
"</tr>"
"<tr>"
"<td></td>"
"<td><c \"#00CCCC\">null</td>"
"<td><c \"#888888\">(toggles)</td>"
"</tr>"
"<tr>"
"<td><c \"#CCCCCC\">current</td>"
"<td></td>"
@ -3796,6 +3968,22 @@ void bot_command_owner_option(Client *c, const Seperator *sep)
c->Message(m_fail, "Bot owner option 'autodefend' is not allowed on this server.");
}
}
else if (!owner_option.compare("buffcounter")) {
if (!argument.compare("enable")) {
c->SetBotOption(Client::booBuffCounter, true);
}
else if (!argument.compare("disable")) {
c->SetBotOption(Client::booBuffCounter, false);
}
else {
c->SetBotOption(Client::booBuffCounter, !c->GetBotOption(Client::booBuffCounter));
}
database.botdb.SaveOwnerOption(c->CharacterID(), Client::booBuffCounter, c->GetBotOption(Client::booBuffCounter));
c->Message(m_action, "Bot 'buff counter' is now %s.", (c->GetBotOption(Client::booBuffCounter) == true ? "enabled" : "disabled"));
}
else if (!owner_option.compare("current")) {
std::string window_title = "Current Bot Owner Options Settings";
@ -3811,13 +3999,15 @@ void bot_command_owner_option(Client *c, const Seperator *sep)
"<tr>" "<td><c \"#CCCCCC\">spawnmessage</td>" "<td><c \"#00CC00\">{}</td>" "</tr>"
"<tr>" "<td><c \"#CCCCCC\">altcombat</td>" "<td><c \"#00CC00\">{}</td>" "</tr>"
"<tr>" "<td><c \"#CCCCCC\">autodefend</td>" "<td><c \"#00CC00\">{}</td>" "</tr>"
"<tr>" "<td><c \"#CCCCCC\">buffcounter</td>" "<td><c \"#00CC00\">{}</td>" "</tr>"
"</table>",
(c->GetBotOption(Client::booDeathMarquee) ? "enabled" : "disabled"),
(c->GetBotOption(Client::booStatsUpdate) ? "enabled" : "disabled"),
(c->GetBotOption(Client::booSpawnMessageSay) ? "say" : (c->GetBotOption(Client::booSpawnMessageTell) ? "tell" : "silent")),
(c->GetBotOption(Client::booSpawnMessageClassSpecific) ? "class" : "default"),
(RuleB(Bots, AllowOwnerOptionAltCombat) ? (c->GetBotOption(Client::booAltCombat) ? "enabled" : "disabled") : "restricted"),
(RuleB(Bots, AllowOwnerOptionAutoDefend) ? (c->GetBotOption(Client::booAutoDefend) ? "enabled" : "disabled") : "restricted")
(RuleB(Bots, AllowOwnerOptionAutoDefend) ? (c->GetBotOption(Client::booAutoDefend) ? "enabled" : "disabled") : "restricted"),
(c->GetBotOption(Client::booBuffCounter) ? "enabled" : "disabled")
);
c->SendPopupToClient(window_title.c_str(), window_text.c_str());
@ -8444,6 +8634,16 @@ bool helper_cast_standard_spell(Bot* casting_bot, Mob* target_mob, int spell_id,
return casting_bot->CastSpell(spell_id, target_mob->GetID(), EQEmu::spells::CastingSlot::Gem2, -1, -1, dont_root_before);
}
bool helper_command_disabled(Client* bot_owner, bool rule_value, const char* command)
{
if (rule_value == false) {
bot_owner->Message(m_fail, "Bot command %s is not enabled on this server.", command);
return true;
}
return false;
}
bool helper_command_alias_fail(Client *bot_owner, const char* command_handler, const char *alias, const char *command)
{
auto alias_iter = bot_command_aliases.find(&alias[1]);

View File

@ -553,6 +553,8 @@ void bot_command_log_command(Client *c, const char *message);
// bot commands
void bot_command_actionable(Client *c, const Seperator *sep);
void bot_command_aggressive(Client *c, const Seperator *sep);
void bot_command_apply_poison(Client *c, const Seperator *sep);
void bot_command_apply_potion(Client* c, const Seperator* sep);
void bot_command_attack(Client *c, const Seperator *sep);
void bot_command_bind_affinity(Client *c, const Seperator *sep);
void bot_command_bot(Client *c, const Seperator *sep);
@ -671,6 +673,7 @@ void helper_bot_appearance_form_update(Bot *my_bot);
uint32 helper_bot_create(Client *bot_owner, std::string bot_name, uint8 bot_class, uint16 bot_race, uint8 bot_gender);
void helper_bot_out_of_combat(Client *bot_owner, Bot *my_bot);
bool helper_cast_standard_spell(Bot* casting_bot, Mob* target_mob, int spell_id, bool annouce_cast = true, uint32* dont_root_before = nullptr);
bool helper_command_disabled(Client *bot_owner, bool rule_value, const char *command);
bool helper_command_alias_fail(Client *bot_owner, const char* command_handler, const char *alias, const char *command);
void helper_command_depart_list(Client* bot_owner, Bot* druid_bot, Bot* wizard_bot, bcst_list* local_list, bool single_flag = false);
bool helper_is_help_or_usage(const char* arg);

View File

@ -2256,6 +2256,7 @@ bool BotDatabase::SaveOwnerOption(const uint32 owner_id, size_t type, const bool
case Client::booSpawnMessageClassSpecific:
case Client::booAltCombat:
case Client::booAutoDefend:
case Client::booBuffCounter:
{
query = fmt::format(
"REPLACE INTO `bot_owner_options`(`owner_id`, `option_type`, `option_value`) VALUES ('{}', '{}', '{}')",

View File

@ -356,6 +356,7 @@ Client::Client(EQStreamInterface* ieqs)
bot_owner_options[booSpawnMessageClassSpecific] = true;
bot_owner_options[booAltCombat] = RuleB(Bots, AllowOwnerOptionAltCombat);
bot_owner_options[booAutoDefend] = RuleB(Bots, AllowOwnerOptionAutoDefend);
bot_owner_options[booBuffCounter] = false;
SetBotPulling(false);
SetBotPrecombat(false);

View File

@ -1640,6 +1640,7 @@ public:
booSpawnMessageClassSpecific,
booAltCombat,
booAutoDefend,
booBuffCounter,
_booCount
};

View File

@ -553,6 +553,7 @@ private:
Mob* GetMobByBotID(uint32 botID);
Bot* GetBotByBotID(uint32 botID);
Bot* GetBotByBotName(std::string botName);
Client* GetBotOwnerByBotEntityID(uint16 entityID);
std::list<Bot*> GetBotsByBotOwnerCharacterID(uint32 botOwnerCharacterID);
bool Bot_AICheckCloseBeneficialSpells(Bot* caster, uint8 iChance, float iRange, uint32 iSpellTypes); // TODO: Evaluate this closesly in hopes to eliminate

View File

@ -5662,11 +5662,24 @@ void Mob::CheckNumHitsRemaining(NumHit type, int32 buff_slot, uint16 spell_id)
bool bDepleted = false;
int buff_max = GetMaxTotalSlots();
#ifdef BOTS
std::string buff_name;
size_t buff_counter = 0;
bool buff_update = false;
#endif
//Spell specific procs [Type 7,10,11]
if (IsValidSpell(spell_id)) {
for (int d = 0; d < buff_max; d++) {
if (buffs[d].spellid == spell_id && buffs[d].numhits > 0 &&
spells[buffs[d].spellid].numhitstype == static_cast<int>(type)) {
#ifdef BOTS
buff_name = spells[buffs[d].spellid].name;
buff_counter = (buffs[d].numhits - 1);
buff_update = true;
#endif
if (--buffs[d].numhits == 0) {
CastOnNumHitFade(buffs[d].spellid);
if (!TryFadeEffect(d))
@ -5679,6 +5692,13 @@ void Mob::CheckNumHitsRemaining(NumHit type, int32 buff_slot, uint16 spell_id)
} else if (type == NumHit::MatchingSpells) {
if (buff_slot >= 0) {
if (--buffs[buff_slot].numhits == 0) {
#ifdef BOTS
buff_name = spells[buffs[buff_slot].spellid].name;
buff_counter = (buffs[buff_slot].numhits - 1);
buff_update = true;
#endif
CastOnNumHitFade(buffs[buff_slot].spellid);
if (!TryFadeEffect(buff_slot))
BuffFadeBySlot(buff_slot , true);
@ -5691,6 +5711,13 @@ void Mob::CheckNumHitsRemaining(NumHit type, int32 buff_slot, uint16 spell_id)
continue;
if (IsValidSpell(buffs[d].spellid) && m_spellHitsLeft[d] == buffs[d].spellid) {
#ifdef BOTS
buff_name = spells[buffs[d].spellid].name;
buff_counter = (buffs[d].numhits - 1);
buff_update = true;
#endif
if (--buffs[d].numhits == 0) {
CastOnNumHitFade(buffs[d].spellid);
m_spellHitsLeft[d] = 0;
@ -5706,6 +5733,13 @@ void Mob::CheckNumHitsRemaining(NumHit type, int32 buff_slot, uint16 spell_id)
for (int d = 0; d < buff_max; d++) {
if (IsValidSpell(buffs[d].spellid) && buffs[d].numhits > 0 &&
spells[buffs[d].spellid].numhitstype == static_cast<int>(type)) {
#ifdef BOTS
buff_name = spells[buffs[d].spellid].name;
buff_counter = (buffs[d].numhits - 1);
buff_update = true;
#endif
if (--buffs[d].numhits == 0) {
CastOnNumHitFade(buffs[d].spellid);
if (!TryFadeEffect(d))
@ -5716,6 +5750,28 @@ void Mob::CheckNumHitsRemaining(NumHit type, int32 buff_slot, uint16 spell_id)
}
}
}
#ifdef BOTS
if (IsBot() && buff_update) {
auto bot_owner = entity_list.GetBotOwnerByBotEntityID(GetID());
if (bot_owner && bot_owner->GetBotOption(Client::booBuffCounter)) {
bot_owner->CastToClient()->SendMarqueeMessage(
Chat::Yellow,
510,
0,
1000,
3000,
StringFormat(
"%s has [%u] hit%s remaining on '%s'",
GetCleanName(),
buff_counter,
(buff_counter == 1 ? "" : "s"),
buff_name.c_str()
)
);
}
}
#endif
}
//for some stupid reason SK procs return theirs one base off...