From 6e11128cbcfef1bbddbfcf10bab93a96d728e95e Mon Sep 17 00:00:00 2001 From: Uleat Date: Fri, 8 Apr 2016 20:58:17 -0400 Subject: [PATCH] Added HealRotation HOT methodology (Heal Override Target) and load/save/delete capabilities --- changelog.txt | 4 + common/version.h | 2 +- .../sql/git/bots/bots_db_update_manifest.txt | 2 + .../2016_04_07_bots_heal_override_target.sql | 3 + .../2016_04_08_bots_heal_rotations.sql | 41 +++ zone/bot.cpp | 21 +- zone/bot_command.cpp | 271 ++++++++++++++++- zone/bot_command.h | 4 + zone/bot_database.cpp | 287 ++++++++++++++++++ zone/bot_database.h | 20 ++ zone/heal_rotation.cpp | 76 ++++- zone/heal_rotation.h | 17 +- 12 files changed, 723 insertions(+), 25 deletions(-) create mode 100644 utils/sql/git/bots/required/2016_04_07_bots_heal_override_target.sql create mode 100644 utils/sql/git/bots/required/2016_04_08_bots_heal_rotations.sql diff --git a/changelog.txt b/changelog.txt index 25729bce1..8563a9a36 100644 --- a/changelog.txt +++ b/changelog.txt @@ -1,5 +1,9 @@ EQEMu Changelog (Started on Sept 24, 2003 15:50) ------------------------------------------------------- +== 04/08/2016 == +Uleat: Added Heal Rotation HOTs (Heal Override Targets) that can be set for proactive healing (default HR behavior is reactive) +Uleat: Added the ability to save/load/delete Heal Rotations based on targeted member - load is automatic when ^hrcreate is used on a bot that has a saved HR entry + == 04/07/2016 == Uleat: Rework of eq_dictionary to facilitate inventory work diff --git a/common/version.h b/common/version.h index 248ce3beb..bebd65ade 100644 --- a/common/version.h +++ b/common/version.h @@ -32,7 +32,7 @@ #define CURRENT_BINARY_DATABASE_VERSION 9096 #ifdef BOTS - #define CURRENT_BINARY_BOTS_DATABASE_VERSION 9003 + #define CURRENT_BINARY_BOTS_DATABASE_VERSION 9005 #else #define CURRENT_BINARY_BOTS_DATABASE_VERSION 0 // must be 0 #endif diff --git a/utils/sql/git/bots/bots_db_update_manifest.txt b/utils/sql/git/bots/bots_db_update_manifest.txt index bbb0130ed..08ef5f161 100644 --- a/utils/sql/git/bots/bots_db_update_manifest.txt +++ b/utils/sql/git/bots/bots_db_update_manifest.txt @@ -2,6 +2,8 @@ 9001|2016_03_24_bots_command_settings.sql|SHOW TABLES LIKE 'bot_command_settings'|empty| 9002|2016_03_24_bots_command_rules.sql|SELECT * FROM `rule_values` WHERE `rule_name` LIKE 'Bots:CommandSpellRank'|empty| 9003|2016_04_05_bots_pet_spell_id_field.sql|SHOW COLUMNS FROM `bot_pets` LIKE 'pet_id'|not_empty| +9004|2016_04_07_bots_heal_override_target.sql|SELECT `bot_command` FROM `bot_command_settings` WHERE `bot_command` LIKE 'healrotationclearhot'|empty| +9005|2016_04_08_bots_heal_rotations.sql|SHOW TABLES LIKE 'bot_heal_rotations'|empty| # Upgrade conditions: # This won't be needed after this system is implemented, but it is used database that are not diff --git a/utils/sql/git/bots/required/2016_04_07_bots_heal_override_target.sql b/utils/sql/git/bots/required/2016_04_07_bots_heal_override_target.sql new file mode 100644 index 000000000..02cc4083e --- /dev/null +++ b/utils/sql/git/bots/required/2016_04_07_bots_heal_override_target.sql @@ -0,0 +1,3 @@ +INSERT INTO `bot_command_settings` VALUES +('healrotationclearhot', 0, 'hrclearhot'), +('healrotationsethot', 0, 'hrsethot'); diff --git a/utils/sql/git/bots/required/2016_04_08_bots_heal_rotations.sql b/utils/sql/git/bots/required/2016_04_08_bots_heal_rotations.sql new file mode 100644 index 000000000..71e0f5af4 --- /dev/null +++ b/utils/sql/git/bots/required/2016_04_08_bots_heal_rotations.sql @@ -0,0 +1,41 @@ +CREATE TABLE `bot_heal_rotations` ( + `heal_rotation_index` INT(11) UNSIGNED NOT NULL AUTO_INCREMENT, + `bot_id` INT(11) UNSIGNED NOT NULL DEFAULT '0', + `interval` INT(11) UNSIGNED NOT NULL DEFAULT '0', + `fast_heals` INT(3) UNSIGNED NOT NULL DEFAULT '0', + `adaptive_targeting` INT(3) UNSIGNED NOT NULL DEFAULT '0', + `casting_override` INT(3) UNSIGNED NOT NULL DEFAULT '0', + `safe_hp_base` FLOAT(11) UNSIGNED NOT NULL DEFAULT '0', + `safe_hp_cloth` FLOAT(11) UNSIGNED NOT NULL DEFAULT '0', + `safe_hp_leather` FLOAT(11) UNSIGNED NOT NULL DEFAULT '0', + `safe_hp_chain` FLOAT(11) UNSIGNED NOT NULL DEFAULT '0', + `safe_hp_plate` FLOAT(11) UNSIGNED NOT NULL DEFAULT '0', + `critical_hp_base` FLOAT(11) UNSIGNED NOT NULL DEFAULT '0', + `critical_hp_cloth` FLOAT(11) UNSIGNED NOT NULL DEFAULT '0', + `critical_hp_leather` FLOAT(11) UNSIGNED NOT NULL DEFAULT '0', + `critical_hp_chain` FLOAT(11) UNSIGNED NOT NULL DEFAULT '0', + `critical_hp_plate` FLOAT(11) UNSIGNED NOT NULL DEFAULT '0', + PRIMARY KEY (`heal_rotation_index`), + CONSTRAINT `FK_bot_heal_rotations` FOREIGN KEY (`bot_id`) REFERENCES `bot_data` (`bot_id`) +) ENGINE=InnoDB DEFAULT CHARSET=latin1; + +CREATE TABLE `bot_heal_rotation_members` ( + `member_index` INT(11) UNSIGNED NOT NULL AUTO_INCREMENT, + `heal_rotation_index` INT(11) UNSIGNED NOT NULL DEFAULT '0', + `bot_id` INT(11) UNSIGNED NOT NULL DEFAULT '0', + PRIMARY KEY (`member_index`), + CONSTRAINT `FK_bot_heal_rotation_members_1` FOREIGN KEY (`heal_rotation_index`) REFERENCES `bot_heal_rotations` (`heal_rotation_index`), + CONSTRAINT `FK_bot_heal_rotation_members_2` FOREIGN KEY (`bot_id`) REFERENCES `bot_data` (`bot_id`) +) ENGINE=InnoDB DEFAULT CHARSET=latin1; + +CREATE TABLE `bot_heal_rotation_targets` ( + `target_index` INT(11) UNSIGNED NOT NULL AUTO_INCREMENT, + `heal_rotation_index` INT(11) UNSIGNED NOT NULL DEFAULT '0', + `target_name` varchar(64) NOT NULL DEFAULT '', + PRIMARY KEY (`target_index`), + CONSTRAINT `FK_bot_heal_rotation_targets` FOREIGN KEY (`heal_rotation_index`) REFERENCES `bot_heal_rotations` (`heal_rotation_index`) +) ENGINE=InnoDB DEFAULT CHARSET=latin1; + +INSERT INTO `bot_command_settings` VALUES +('healrotationdelete', 0, 'hrdelete'), +('healrotationsave', 0, 'hrsave'); diff --git a/zone/bot.cpp b/zone/bot.cpp index 2c22a12a6..925cf6a2c 100644 --- a/zone/bot.cpp +++ b/zone/bot.cpp @@ -1597,6 +1597,25 @@ bool Bot::DeleteBot() if (!bot_owner) return false; + if (!botdb.DeleteHealRotation(GetBotID())) { + bot_owner->Message(13, "%s", BotDatabase::fail::DeleteHealRotation()); + return false; + } + + std::string query = StringFormat("DELETE FROM `bot_heal_rotation_members` WHERE `bot_id` = '%u'", GetBotID()); + auto results = botdb.QueryDatabase(query); + if (!results.Success()) { + bot_owner->Message(13, "Failed to delete heal rotation member '%s'", GetCleanName()); + return false; + } + + query = StringFormat("DELETE FROM `bot_heal_rotation_targets` WHERE `target_name` LIKE '%s'", GetCleanName()); + results = botdb.QueryDatabase(query); + if (!results.Success()) { + bot_owner->Message(13, "Failed to delete heal rotation target '%s'", GetCleanName()); + return false; + } + if (!DeletePet()) { bot_owner->Message(13, "Failed to delete pet for '%s'", GetCleanName()); return false; @@ -8358,7 +8377,7 @@ bool Bot::IsMyHealRotationSet() { if (!IsHealRotationMember()) return false; - if (!m_member_of_heal_rotation->IsActive()) + if (!m_member_of_heal_rotation->IsActive() && !m_member_of_heal_rotation->IsHOTActive()) return false; if (!m_member_of_heal_rotation->CastingReady()) return false; diff --git a/zone/bot_command.cpp b/zone/bot_command.cpp index c69cfda44..5422c41ab 100644 --- a/zone/bot_command.cpp +++ b/zone/bot_command.cpp @@ -1370,13 +1370,17 @@ int bot_command_init(void) bot_command_add("healrotationadjustsafe", "Adjusts the safe HP limit of the heal rotation instance's Class Armor Type criteria", 0, bot_subcommand_heal_rotation_adjust_safe) || bot_command_add("healrotationcastingoverride", "Enables or disables casting overrides within the heal rotation instance", 0, bot_subcommand_heal_rotation_casting_override) || bot_command_add("healrotationchangeinterval", "Changes casting interval between members within the heal rotation instance", 0, bot_subcommand_heal_rotation_change_interval) || + bot_command_add("healrotationclearhot", "Clears the HOT of a heal rotation instance", 0, bot_subcommand_heal_rotation_clear_hot) || bot_command_add("healrotationcleartargets", "Removes all targets from a heal rotation instance", 0, bot_subcommand_heal_rotation_clear_targets) || bot_command_add("healrotationcreate", "Creates a bot heal rotation instance and designates a leader", 0, bot_subcommand_heal_rotation_create) || + bot_command_add("healrotationdelete", "Deletes a bot heal rotation entry by leader", 0, bot_subcommand_heal_rotation_delete) || bot_command_add("healrotationfastheals", "Enables or disables fast heals within the heal rotation instance", 0, bot_subcommand_heal_rotation_fast_heals) || bot_command_add("healrotationlist", "Reports heal rotation instance(s) information", 0, bot_subcommand_heal_rotation_list) || bot_command_add("healrotationremovemember", "Removes a bot from a heal rotation instance", 0, bot_subcommand_heal_rotation_remove_member) || bot_command_add("healrotationremovetarget", "Removes target from a heal rotations instance", 0, bot_subcommand_heal_rotation_remove_target) || bot_command_add("healrotationresetlimits", "Resets all Class Armor Type HP limit criteria in a heal rotation to its default value", 0, bot_subcommand_heal_rotation_reset_limits) || + bot_command_add("healrotationsave", "Saves a bot heal rotation entry by leader", 0, bot_subcommand_heal_rotation_save) || + bot_command_add("healrotationsethot", "Sets the HOT in a heal rotation instance", 0, bot_subcommand_heal_rotation_set_hot) || bot_command_add("healrotationstart", "Starts a heal rotation", 0, bot_subcommand_heal_rotation_start) || bot_command_add("healrotationstop", "Stops a heal rotation", 0, bot_subcommand_heal_rotation_stop) || bot_command_add("help", "List available commands and their description - specify partial command as argument to search", 0, bot_command_help) || @@ -3064,13 +3068,17 @@ void bot_command_heal_rotation(Client *c, const Seperator *sep) subcommand_list.push_back("healrotationadjustsafe"); subcommand_list.push_back("healrotationcastoverride"); subcommand_list.push_back("healrotationchangeinterval"); + subcommand_list.push_back("healrotationclearhot"); subcommand_list.push_back("healrotationcleartargets"); subcommand_list.push_back("healrotationcreate"); + subcommand_list.push_back("healrotationdelete"); subcommand_list.push_back("healrotationfastheals"); subcommand_list.push_back("healrotationlist"); subcommand_list.push_back("healrotationremovemember"); subcommand_list.push_back("healrotationremovetarget"); subcommand_list.push_back("healrotationresetlimits"); + subcommand_list.push_back("healrotationsave"); + subcommand_list.push_back("healrotationsethot"); subcommand_list.push_back("healrotationstart"); subcommand_list.push_back("healrotationstop"); /* VS2012 code - end */ @@ -3078,8 +3086,9 @@ void bot_command_heal_rotation(Client *c, const Seperator *sep) /* VS2013 code const std::list subcommand_list = { "healrotationadaptivetargeting", "healrotationaddmember", "healrotationaddtarget", "healrotationadjustcritical", "healrotationadjustsafe", - "healrotationcastoverride", "healrotationchangeinterval", "healrotationcleartargets", "healrotationcreate", "healrotationfastheals", - "healrotationlist", "healrotationremovemember", "healrotationremovetarget", "healrotationresetlimits", "healrotationstart", "healrotationstop" + "healrotationcastoverride", "healrotationchangeinterval", "healrotationclearhot", "healrotationcleartargets", "healrotationcreate", + "healrotationdelete", "healrotationfastheals", "healrotationlist", "healrotationremovemember", "healrotationremovetarget", "healrotationsave", + "healrotationresetlimits", "healrotationsethot", "healrotationstart", "healrotationstop" }; */ @@ -6132,10 +6141,10 @@ void bot_subcommand_heal_rotation_adjust_critical(Client *c, const Seperator *se else if (!critical_arg.compare("-")) critical_ratio = (*current_member->MemberOfHealRotation())->ArmorTypeCriticalHPRatio(armor_type_value) - HP_RATIO_DELTA; - if (critical_ratio > SAFE_HP_RATIO_BASE) - critical_ratio = SAFE_HP_RATIO_BASE; - if (critical_ratio < CRITICAL_HP_RATIO_BASE) - critical_ratio = CRITICAL_HP_RATIO_BASE; + if (critical_ratio > SAFE_HP_RATIO_ABS) + critical_ratio = SAFE_HP_RATIO_ABS; + if (critical_ratio < CRITICAL_HP_RATIO_ABS) + critical_ratio = CRITICAL_HP_RATIO_ABS; if (!(*current_member->MemberOfHealRotation())->SetArmorTypeCriticalHPRatio(armor_type_value, critical_ratio)) { c->Message(m_fail, "Critical value %3.1f%%(%u) exceeds safe value %3.1f%%(%u) for %s's Heal Rotation", @@ -6198,10 +6207,10 @@ void bot_subcommand_heal_rotation_adjust_safe(Client *c, const Seperator *sep) else if (!safe_arg.compare("-")) safe_ratio = (*current_member->MemberOfHealRotation())->ArmorTypeSafeHPRatio(armor_type_value) - HP_RATIO_DELTA; - if (safe_ratio > SAFE_HP_RATIO_BASE) - safe_ratio = SAFE_HP_RATIO_BASE; - if (safe_ratio < CRITICAL_HP_RATIO_BASE) - safe_ratio = CRITICAL_HP_RATIO_BASE; + if (safe_ratio > SAFE_HP_RATIO_ABS) + safe_ratio = SAFE_HP_RATIO_ABS; + if (safe_ratio < CRITICAL_HP_RATIO_ABS) + safe_ratio = CRITICAL_HP_RATIO_ABS; if (!(*current_member->MemberOfHealRotation())->SetArmorTypeSafeHPRatio(armor_type_value, safe_ratio)) { c->Message(m_fail, "Safe value %3.1f%%(%u) does not exceed critical value %3.1f%%(%u) for %s's Heal Rotation", @@ -6256,13 +6265,13 @@ void bot_subcommand_heal_rotation_casting_override(Client *c, const Seperator *s hr_casting_override = true; } else if (casting_override_arg.compare("off")) { - c->Message(m_action, "Fast heals are currently '%s' for %s's Heal Rotation", (((*current_member->MemberOfHealRotation())->CastingOverride()) ? ("on") : ("off")), current_member->GetCleanName()); + c->Message(m_action, "Casting override is currently '%s' for %s's Heal Rotation", (((*current_member->MemberOfHealRotation())->CastingOverride()) ? ("on") : ("off")), current_member->GetCleanName()); return; } (*current_member->MemberOfHealRotation())->SetCastingOverride(hr_casting_override); - c->Message(m_action, "Fast heals are now '%s' for %s's Heal Rotation", (((*current_member->MemberOfHealRotation())->CastingOverride()) ? ("on") : ("off")), current_member->GetCleanName()); + c->Message(m_action, "Casting override is now '%s' for %s's Heal Rotation", (((*current_member->MemberOfHealRotation())->CastingOverride()) ? ("on") : ("off")), current_member->GetCleanName()); } void bot_subcommand_heal_rotation_change_interval(Client *c, const Seperator *sep) @@ -6323,6 +6332,42 @@ void bot_subcommand_heal_rotation_change_interval(Client *c, const Seperator *se c->Message(m_action, "Casting interval is now '%i' second%s for %s's Heal Rotation", hr_change_interval_s, ((hr_change_interval_s == 1) ? ("") : ("s")), current_member->GetCleanName()); } +void bot_subcommand_heal_rotation_clear_hot(Client *c, const Seperator *sep) +{ + if (helper_command_alias_fail(c, "bot_subcommand_heal_rotation_clear_hot", sep->arg[0], "healrotationclearhot")) + return; + if (helper_is_help_or_usage(sep->arg[1])) { + c->Message(m_usage, "usage: () %s ([member_name])", sep->arg[0]); + return; + } + + std::list sbl; + MyBots::PopulateSBL_ByNamedBot(c, sbl, sep->arg[1]); + if (sbl.empty()) + MyBots::PopulateSBL_ByTargetedBot(c, sbl); + if (sbl.empty()) { + c->Message(m_fail, "You must or [name] a current member as a bot that you own to use this command"); + return; + } + + auto current_member = sbl.front(); + if (!current_member) { + c->Message(m_unknown, "Error: Current member bot dereferenced to nullptr"); + return; + } + + if (!current_member->IsHealRotationMember()) { + c->Message(m_fail, "%s is not a current member of a Heal Rotation", current_member->GetCleanName()); + return; + } + + if (!(*current_member->MemberOfHealRotation())->ClearHOTTarget()) { + c->Message(m_fail, "Failed to clear %s's Heal Rotation HOT", current_member->GetCleanName()); + } + + c->Message(m_action, "Succeeded in clearing %s's Heal Rotation HOT", current_member->GetCleanName()); +} + void bot_subcommand_heal_rotation_clear_targets(Client *c, const Seperator *sep) { if (helper_command_alias_fail(c, "bot_subcommand_heal_rotation_clear_targets", sep->arg[0], "healrotationcleartargets")) @@ -6437,8 +6482,115 @@ void bot_subcommand_heal_rotation_create(Client *c, const Seperator *sep) c->Message(m_fail, "Failed to add %s as a current member to a new Heal Rotation", creator_member->GetCleanName()); return; } + + std::list member_list; + std::list target_list; + bool load_flag = false; + bool member_fail = false; + bool target_fail = false; + + if (!botdb.LoadHealRotation(creator_member, member_list, target_list, load_flag, member_fail, target_fail)) + c->Message(m_fail, "%s", BotDatabase::fail::LoadHealRotation()); - c->Message(m_action, "Successfully added %s as a current member to a new Heal Rotation", creator_member->GetCleanName()); + if (!load_flag) { + c->Message(m_action, "Successfully added %s as a current member to a new Heal Rotation", creator_member->GetCleanName()); + return; + } + + if (!member_fail) { + MyBots::PopulateSBL_BySpawnedBots(c, sbl); + for (auto member_iter : member_list) { + if (!member_iter || member_iter == creator_member->GetBotID()) + continue; + + bool member_found = false; + for (auto bot_iter : sbl) { + if (bot_iter->GetBotID() != member_iter) + continue; + + if (!bot_iter->JoinHealRotationMemberPool(creator_member->MemberOfHealRotation())) + c->Message(m_fail, "Failed to add member '%s'", bot_iter->GetCleanName()); + member_found = true; + + break; + } + + if (!member_found) + c->Message(m_fail, "Could not locate member with bot id '%u'", member_iter); + } + } + else { + c->Message(m_fail, "%s", BotDatabase::fail::LoadHealRotationMembers()); + } + + if (!target_fail) { + for (auto target_iter : target_list) { + if (target_iter.empty()) + continue; + + auto target_mob = entity_list.GetMob(target_iter.c_str()); + if (!target_mob) { + c->Message(m_fail, "Could not locate target '%s'", target_iter.c_str()); + continue; + } + + if (!target_mob->JoinHealRotationTargetPool(creator_member->MemberOfHealRotation())) + c->Message(m_fail, "Failed to add target '%s'", target_mob->GetCleanName()); + } + } + else { + c->Message(m_fail, "%s", BotDatabase::fail::LoadHealRotationTargets()); + } + + c->Message(m_action, "Successfully loaded %s's Heal Rotation", creator_member->GetCleanName()); +} + +void bot_subcommand_heal_rotation_delete(Client *c, const Seperator *sep) +{ + if (helper_command_alias_fail(c, "bot_subcommand_heal_rotation_delete", sep->arg[0], "healrotationdelete")) + return; + if (helper_is_help_or_usage(sep->arg[1])) { + c->Message(m_usage, "usage: () %s ([option: all]) ([member_name])", sep->arg[0]); + return; + } + + bool all_flag = false; + int name_arg = 1; + if (!strcasecmp(sep->arg[1], "all")) { + all_flag = true; + name_arg = 2; + } + + if (all_flag) { + if (botdb.DeleteAllHealRotations(c->CharacterID())) + c->Message(m_action, "Succeeded in deleting all heal rotations"); + else + c->Message(m_fail, "%s", BotDatabase::fail::DeleteAllHealRotations()); + + return; + } + + std::list sbl; + MyBots::PopulateSBL_ByNamedBot(c, sbl, sep->arg[name_arg]); + if (sbl.empty()) + MyBots::PopulateSBL_ByTargetedBot(c, sbl); + if (sbl.empty()) { + c->Message(m_fail, "You must or [name] a current member as a bot that you own to use this command"); + return; + } + + auto current_member = sbl.front(); + if (!current_member) { + c->Message(m_unknown, "Error: Current member bot dereferenced to nullptr"); + return; + } + + if (!botdb.DeleteHealRotation(current_member->GetBotID())) { + c->Message(m_fail, "%s", BotDatabase::fail::DeleteHealRotation()); + return; + } + + c->Message(m_action, "Succeeded in deleting %s's heal rotation", current_member->GetCleanName()); } void bot_subcommand_heal_rotation_fast_heals(Client *c, const Seperator *sep) @@ -6529,6 +6681,8 @@ void bot_subcommand_heal_rotation_list(Client *c, const Seperator *sep) c->Message(m_message, "Fast heals: '%s'", (((*current_member->MemberOfHealRotation())->FastHeals()) ? ("on") : ("off"))); c->Message(m_message, "Adaptive targeting: '%s'", (((*current_member->MemberOfHealRotation())->AdaptiveTargeting()) ? ("on") : ("off"))); c->Message(m_message, "Casting override: '%s'", (((*current_member->MemberOfHealRotation())->CastingOverride()) ? ("on") : ("off"))); + c->Message(m_message, "HOT state: %s", (((*current_member->MemberOfHealRotation())->IsHOTActive()) ? ("active") : ("inactive"))); + c->Message(m_message, "HOT target: %s", (((*current_member->MemberOfHealRotation())->HOTTarget()) ? ((*current_member->MemberOfHealRotation())->HOTTarget()->GetCleanName()) : ("null"))); c->Message(m_message, "Base hp limits - critical: %3.1f%%, safe: %3.1f%%", (*current_member->MemberOfHealRotation())->ArmorTypeCriticalHPRatio(ARMOR_TYPE_UNKNOWN), @@ -6687,6 +6841,97 @@ void bot_subcommand_heal_rotation_reset_limits(Client *c, const Seperator *sep) c->Message(m_action, "Class Armor Type HP limit criteria has been set to default values for %s's Heal Rotation", current_member->GetCleanName()); } +void bot_subcommand_heal_rotation_save(Client *c, const Seperator *sep) +{ + if (helper_command_alias_fail(c, "bot_subcommand_heal_rotation_save", sep->arg[0], "healrotationsave")) + return; + if (helper_is_help_or_usage(sep->arg[1])) { + c->Message(m_usage, "usage: () %s ([member_name])", sep->arg[0]); + return; + } + + std::list sbl; + MyBots::PopulateSBL_ByNamedBot(c, sbl, sep->arg[1]); + if (sbl.empty()) + MyBots::PopulateSBL_ByTargetedBot(c, sbl); + if (sbl.empty()) { + c->Message(m_fail, "You must or [name] a current member as a bot that you own to use this command"); + return; + } + + auto current_member = sbl.front(); + if (!current_member) { + c->Message(m_unknown, "Error: Current member bot dereferenced to nullptr"); + return; + } + + if (!current_member->IsHealRotationMember()) { + c->Message(m_fail, "%s is not a current member of a Heal Rotation", current_member->GetCleanName()); + return; + } + + bool member_fail = false; + bool target_fail = false; + if (!botdb.SaveHealRotation(current_member, member_fail, target_fail)) { + c->Message(m_fail, "%s", BotDatabase::fail::SaveHealRotation()); + return; + } + if (member_fail) + c->Message(m_fail, "Failed to save heal rotation members"); + if (target_fail) + c->Message(m_fail, "Failed to save heal rotation targets"); + + c->Message(m_action, "Succeeded in saving %s's heal rotation", current_member->GetCleanName()); +} + +void bot_subcommand_heal_rotation_set_hot(Client *c, const Seperator *sep) +{ + if (helper_command_alias_fail(c, "bot_subcommand_heal_rotation_set_hot", sep->arg[0], "healrotationsethot")) + return; + if (helper_is_help_or_usage(sep->arg[1])) { + c->Message(m_usage, "usage: () %s [heal_override_target_name] ([member_name])", sep->arg[0]); + return; + } + + std::list sbl; + MyBots::PopulateSBL_ByNamedBot(c, sbl, sep->arg[2]); + if (sbl.empty()) + MyBots::PopulateSBL_ByTargetedBot(c, sbl); + if (sbl.empty()) { + c->Message(m_fail, "You must or [name] a current member as a bot that you own to use this command"); + return; + } + + auto current_member = sbl.front(); + if (!current_member) { + c->Message(m_unknown, "Error: Current member bot dereferenced to nullptr"); + return; + } + + if (!current_member->IsHealRotationMember()) { + c->Message(m_fail, "%s is not a member of a Heal Rotation", current_member->GetCleanName()); + return; + } + + auto hot_target = entity_list.GetMob(sep->arg[1]); + if (!hot_target) { + c->Message(m_fail, "No target exists by the name '%s'", sep->arg[1]); + return; + } + + if (!(*current_member->MemberOfHealRotation())->IsTargetInPool(hot_target)) { + c->Message(m_fail, "%s is not a target in %s's Heal Rotation", hot_target->GetCleanName(), current_member->GetCleanName()); + return; + } + + if (!(*current_member->MemberOfHealRotation())->SetHOTTarget(hot_target)) { + c->Message(m_fail, "Failed to set %s as the HOT in %s's Heal Rotation", hot_target->GetCleanName(), current_member->GetCleanName()); + return; + } + + c->Message(m_action, "Succeeded in setting %s as the HOT in %s's Heal Rotation", hot_target->GetCleanName(), current_member->GetCleanName()); +} + void bot_subcommand_heal_rotation_start(Client *c, const Seperator *sep) { if (helper_command_alias_fail(c, "bot_subcommand_heal_rotation_start", sep->arg[0], "healrotationstart")) diff --git a/zone/bot_command.h b/zone/bot_command.h index bc8558ff1..6c7d66a9c 100644 --- a/zone/bot_command.h +++ b/zone/bot_command.h @@ -633,13 +633,17 @@ void bot_subcommand_heal_rotation_adjust_critical(Client *c, const Seperator *se void bot_subcommand_heal_rotation_adjust_safe(Client *c, const Seperator *sep); void bot_subcommand_heal_rotation_casting_override(Client *c, const Seperator *sep); void bot_subcommand_heal_rotation_change_interval(Client *c, const Seperator *sep); +void bot_subcommand_heal_rotation_clear_hot(Client *c, const Seperator *sep); void bot_subcommand_heal_rotation_clear_targets(Client *c, const Seperator *sep); void bot_subcommand_heal_rotation_create(Client *c, const Seperator *sep); +void bot_subcommand_heal_rotation_delete(Client *c, const Seperator *sep); void bot_subcommand_heal_rotation_fast_heals(Client *c, const Seperator *sep); void bot_subcommand_heal_rotation_list(Client *c, const Seperator *sep); void bot_subcommand_heal_rotation_remove_member(Client *c, const Seperator *sep); void bot_subcommand_heal_rotation_remove_target(Client *c, const Seperator *sep); void bot_subcommand_heal_rotation_reset_limits(Client *c, const Seperator *sep); +void bot_subcommand_heal_rotation_save(Client *c, const Seperator *sep); +void bot_subcommand_heal_rotation_set_hot(Client *c, const Seperator *sep); void bot_subcommand_heal_rotation_start(Client *c, const Seperator *sep); void bot_subcommand_heal_rotation_stop(Client *c, const Seperator *sep); void bot_subcommand_inventory_give(Client *c, const Seperator *sep); diff --git a/zone/bot_database.cpp b/zone/bot_database.cpp index 922259cab..f8ab5c3d2 100644 --- a/zone/bot_database.cpp +++ b/zone/bot_database.cpp @@ -2407,6 +2407,284 @@ bool BotDatabase::LoadGroupedBotsByGroupID(const uint32 group_id, std::list& member_list, std::list& target_list, bool& load_flag, bool& member_fail, bool& target_fail) +{ + if (!hr_member) + return false; + + uint32 hr_index = 0; + if (!LoadHealRotationIDByBotID(hr_member->GetBotID(), hr_index)) + return false; + if (!hr_index) + return true; + + if (!hr_member->IsHealRotationMember()) + return false; + + query = StringFormat( + "SELECT " + " `interval`," + " `fast_heals`," + " `adaptive_targeting`," + " `casting_override`," + " `safe_hp_base`," + " `safe_hp_cloth`," + " `safe_hp_leather`," + " `safe_hp_chain`," + " `safe_hp_plate`," + " `critical_hp_base`," + " `critical_hp_cloth`," + " `critical_hp_leather`," + " `critical_hp_chain`," + " `critical_hp_plate`" + " FROM `bot_heal_rotations`" + " WHERE `heal_rotation_index` = '%u'" + " LIMIT 1", + hr_index + ); + auto results = QueryDatabase(query); + if (!results.Success()) + return false; + if (!results.RowCount()) + return true; + + auto row = results.begin(); + (*hr_member->MemberOfHealRotation())->SetIntervalS((uint32)atoi(row[0])); + (*hr_member->MemberOfHealRotation())->SetFastHeals((bool)atoi(row[1])); + (*hr_member->MemberOfHealRotation())->SetAdaptiveTargeting((bool)atoi(row[2])); + (*hr_member->MemberOfHealRotation())->SetCastingOverride((bool)atoi(row[3])); + (*hr_member->MemberOfHealRotation())->SetArmorTypeSafeHPRatio(ARMOR_TYPE_UNKNOWN, atof(row[4])); + (*hr_member->MemberOfHealRotation())->SetArmorTypeSafeHPRatio(ARMOR_TYPE_CLOTH, atof(row[5])); + (*hr_member->MemberOfHealRotation())->SetArmorTypeSafeHPRatio(ARMOR_TYPE_LEATHER, atof(row[6])); + (*hr_member->MemberOfHealRotation())->SetArmorTypeSafeHPRatio(ARMOR_TYPE_CHAIN, atof(row[7])); + (*hr_member->MemberOfHealRotation())->SetArmorTypeSafeHPRatio(ARMOR_TYPE_PLATE, atof(row[8])); + (*hr_member->MemberOfHealRotation())->SetArmorTypeCriticalHPRatio(ARMOR_TYPE_UNKNOWN, atof(row[9])); + (*hr_member->MemberOfHealRotation())->SetArmorTypeCriticalHPRatio(ARMOR_TYPE_CLOTH, atof(row[10])); + (*hr_member->MemberOfHealRotation())->SetArmorTypeCriticalHPRatio(ARMOR_TYPE_LEATHER, atof(row[11])); + (*hr_member->MemberOfHealRotation())->SetArmorTypeCriticalHPRatio(ARMOR_TYPE_CHAIN, atof(row[12])); + (*hr_member->MemberOfHealRotation())->SetArmorTypeCriticalHPRatio(ARMOR_TYPE_PLATE, atof(row[13])); + + load_flag = true; + + if (!LoadHealRotationMembers(hr_index, member_list)) + member_fail = true; + + if (!LoadHealRotationTargets(hr_index, target_list)) + target_fail = true; + + return true; +} + +bool BotDatabase::LoadHealRotationMembers(const uint32 hr_index, std::list& member_list) +{ + if (!hr_index) + return false; + + query = StringFormat("SELECT `bot_id` FROM `bot_heal_rotation_members` WHERE `heal_rotation_index` = '%u'", hr_index); + auto results = QueryDatabase(query); + if (!results.Success()) + return false; + if (!results.RowCount()) + return true; + + for (auto row : results) { + if (row[0]) + member_list.push_back(atoi(row[0])); + } + + return true; +} + +bool BotDatabase::LoadHealRotationTargets(const uint32 hr_index, std::list& target_list) +{ + if (!hr_index) + return false; + + query = StringFormat("SELECT `target_name` FROM `bot_heal_rotation_targets` WHERE `heal_rotation_index` = '%u'", hr_index); + auto results = QueryDatabase(query); + if (!results.Success()) + return false; + if (!results.RowCount()) + return true; + + for (auto row : results) { + if (row[0]) + target_list.push_back(row[0]); + } + + return true; +} + +bool BotDatabase::SaveHealRotation(Bot* hr_member, bool& member_fail, bool& target_fail) +{ + if (!hr_member) + return false; + + if (!DeleteHealRotation(hr_member->GetBotID())) + return false; + + if (!hr_member->IsHealRotationMember()) + return false; + + query = StringFormat( + "INSERT INTO `bot_heal_rotations` (" + "`bot_id`," + " `interval`," + " `fast_heals`," + " `adaptive_targeting`," + " `casting_override`," + " `safe_hp_base`," + " `safe_hp_cloth`," + " `safe_hp_leather`," + " `safe_hp_chain`," + " `safe_hp_plate`," + " `critical_hp_base`," + " `critical_hp_cloth`," + " `critical_hp_leather`," + " `critical_hp_chain`," + " `critical_hp_plate`" + ")" + " VALUES (" + "'%u'," + " '%u'," + " '%u'," + " '%u'," + " '%u'," + " '%f'," + " '%f'," + " '%f'," + " '%f'," + " '%f'," + " '%f'," + " '%f'," + " '%f'," + " '%f'," + " '%f'" + ")", + hr_member->GetBotID(), + ((*hr_member->MemberOfHealRotation())->IntervalS()), + ((*hr_member->MemberOfHealRotation())->FastHeals()), + ((*hr_member->MemberOfHealRotation())->AdaptiveTargeting()), + ((*hr_member->MemberOfHealRotation())->CastingOverride()), + ((*hr_member->MemberOfHealRotation())->ArmorTypeSafeHPRatio(ARMOR_TYPE_UNKNOWN)), + ((*hr_member->MemberOfHealRotation())->ArmorTypeSafeHPRatio(ARMOR_TYPE_CLOTH)), + ((*hr_member->MemberOfHealRotation())->ArmorTypeSafeHPRatio(ARMOR_TYPE_LEATHER)), + ((*hr_member->MemberOfHealRotation())->ArmorTypeSafeHPRatio(ARMOR_TYPE_CHAIN)), + ((*hr_member->MemberOfHealRotation())->ArmorTypeSafeHPRatio(ARMOR_TYPE_PLATE)), + ((*hr_member->MemberOfHealRotation())->ArmorTypeCriticalHPRatio(ARMOR_TYPE_UNKNOWN)), + ((*hr_member->MemberOfHealRotation())->ArmorTypeCriticalHPRatio(ARMOR_TYPE_CLOTH)), + ((*hr_member->MemberOfHealRotation())->ArmorTypeCriticalHPRatio(ARMOR_TYPE_LEATHER)), + ((*hr_member->MemberOfHealRotation())->ArmorTypeCriticalHPRatio(ARMOR_TYPE_CHAIN)), + ((*hr_member->MemberOfHealRotation())->ArmorTypeCriticalHPRatio(ARMOR_TYPE_PLATE)) + ); + auto results = QueryDatabase(query); + if (!results.Success()) + return false; + + uint32 hr_index = results.LastInsertedID(); + if (!hr_index) + return false; + + std::list* member_list = (*hr_member->MemberOfHealRotation())->MemberList(); + + for (auto member_iter : *member_list) { + if (!member_iter) + continue; + + query = StringFormat("INSERT INTO `bot_heal_rotation_members` (`heal_rotation_index`, `bot_id`) VALUES ('%u', '%u')", hr_index, member_iter->GetBotID()); + auto results = QueryDatabase(query); + if (!results.Success()) { + member_fail = true; + break; + } + } + + std::list* target_list = (*hr_member->MemberOfHealRotation())->TargetList(); + + for (auto target_iter : *target_list) { + if (!target_iter) + continue; + + query = StringFormat("INSERT INTO `bot_heal_rotation_targets` (`heal_rotation_index`, `target_name`) VALUES ('%u', '%s')", hr_index, target_iter->GetCleanName()); + auto results = QueryDatabase(query); + if (!results.Success()) { + target_fail = true; + break; + } + } + + return true; +} + +bool BotDatabase::DeleteHealRotation(const uint32 creator_id) +{ + if (!creator_id) + return false; + + uint32 hr_index = 0; + if (!LoadHealRotationIDByBotID(creator_id, hr_index)) + return false; + if (!hr_index) + return true; + + query = StringFormat("DELETE FROM `bot_heal_rotation_targets` WHERE `heal_rotation_index` = '%u'", hr_index); + auto results = QueryDatabase(query); + if (!results.Success()) + return false; + + query = StringFormat("DELETE FROM `bot_heal_rotation_members` WHERE `heal_rotation_index` = '%u'", hr_index); + results = QueryDatabase(query); + if (!results.Success()) + return false; + + query = StringFormat("DELETE FROM `bot_heal_rotations` WHERE `heal_rotation_index` = '%u'", hr_index); + results = QueryDatabase(query); + if (!results.Success()) + return false; + + return true; +} + +bool BotDatabase::DeleteAllHealRotations(const uint32 owner_id) +{ + if (!owner_id) + return false; + + query = StringFormat("SELECT `bot_id` FROM `bot_heal_rotations` WHERE `bot_id` IN (SELECT `bot_id` FROM `bot_data` WHERE `owner_id` = '%u')", owner_id); + auto results = QueryDatabase(query); + if (!results.Success()) + return false; + + for (auto row : results) { + if (!row[0]) + continue; + + DeleteHealRotation(atoi(row[0])); + } + + return true; +} + + /* Bot miscellaneous functions */ @@ -2495,6 +2773,15 @@ const char* BotDatabase::fail::LoadBotGroupsListByOwnerID() { return "Failed to /* fail::Bot group functions */ const char* BotDatabase::fail::LoadGroupedBotsByGroupID() { return "Failed to load grouped bots by group id"; } +/* fail::Bot heal rotation functions */ +const char* BotDatabase::fail::LoadHealRotationIDByBotID() { return "Failed to load heal rotation id by bot id"; } +const char* BotDatabase::fail::LoadHealRotation() { return "Failed to load heal rotation"; } +const char* BotDatabase::fail::LoadHealRotationMembers() { return "Failed to load heal rotation members"; } +const char* BotDatabase::fail::LoadHealRotationTargets() { return "Failed to load heal rotation targets"; } +const char* BotDatabase::fail::SaveHealRotation() { return "Failed to save heal rotation"; } +const char* BotDatabase::fail::DeleteHealRotation() { return "Failed to delete heal rotation"; } +const char* BotDatabase::fail::DeleteAllHealRotations() { return "Failed to delete all heal rotations"; } + /* fail::Bot miscellaneous functions */ #endif // BOTS diff --git a/zone/bot_database.h b/zone/bot_database.h index 27d4b4116..5ade661c2 100644 --- a/zone/bot_database.h +++ b/zone/bot_database.h @@ -167,6 +167,17 @@ public: bool LoadGroupedBotsByGroupID(const uint32 group_id, std::list& group_list); + /* Bot heal rotation functions */ + bool LoadHealRotationIDByBotID(const uint32 bot_id, uint32& hr_index); + + bool LoadHealRotation(Bot* hr_member, std::list& member_list, std::list& target_list, bool& load_flag, bool& member_fail, bool& target_fail); + bool LoadHealRotationMembers(const uint32 hr_index, std::list& member_list); + bool LoadHealRotationTargets(const uint32 hr_index, std::list& target_list); + bool SaveHealRotation(Bot* hr_member, bool& member_fail, bool& target_fail); + bool DeleteHealRotation(const uint32 creator_id); + + bool DeleteAllHealRotations(const uint32 owner_id); + /* Bot miscellaneous functions */ @@ -257,6 +268,15 @@ public: /* fail::Bot group functions */ static const char* LoadGroupedBotsByGroupID(); + /* fail::Bot heal rotation functions */ + static const char* LoadHealRotationIDByBotID(); + static const char* LoadHealRotation(); + static const char* LoadHealRotationMembers(); + static const char* LoadHealRotationTargets(); + static const char* SaveHealRotation(); + static const char* DeleteHealRotation(); + static const char* DeleteAllHealRotations(); + /* fail::Bot miscellaneous functions */ }; diff --git a/zone/heal_rotation.cpp b/zone/heal_rotation.cpp index 4e22e5188..6981cc0c1 100644 --- a/zone/heal_rotation.cpp +++ b/zone/heal_rotation.cpp @@ -25,10 +25,10 @@ #define SAFE_HP_RATIO_CHAIN 80.0f #define SAFE_HP_RATIO_PLATE 75.0f -#define CRITICAL_HP_RATIO_CLOTH 30.0f -#define CRITICAL_HP_RATIO_LEATHER 25.0f -#define CRITICAL_HP_RATIO_CHAIN 15.0f -#define CRITICAL_HP_RATIO_PLATE 10.0f +#define CRITICAL_HP_RATIO_CLOTH 45.0f +#define CRITICAL_HP_RATIO_LEATHER 40.0f +#define CRITICAL_HP_RATIO_CHAIN 35.0f +#define CRITICAL_HP_RATIO_PLATE 30.0f HealRotation::HealRotation(Bot* hr_creator, uint32 interval_ms, bool fast_heals, bool adaptive_targeting, bool casting_override) { @@ -51,6 +51,9 @@ HealRotation::HealRotation(Bot* hr_creator, uint32 interval_ms, bool fast_heals, m_is_active = false; m_consumed = false; + + m_hot_target = nullptr; + m_hot_active = false; } void HealRotation::SetIntervalMS(uint32 interval_ms) @@ -143,6 +146,11 @@ bool HealRotation::RemoveTargetFromPool(Mob* hr_target) if (target_iter != hr_target) continue; + if (m_hot_target == hr_target) { + m_hot_target = nullptr; + m_hot_active = false; + } + m_target_healing_stats_2.erase(hr_target); m_target_healing_stats_1.erase(hr_target); m_target_pool.remove(hr_target); @@ -172,6 +180,8 @@ bool HealRotation::ClearMemberPool() bool HealRotation::ClearTargetPool() { + m_hot_target = nullptr; + m_hot_active = false; m_is_active = false; auto clear_list = m_target_pool; @@ -184,11 +194,32 @@ bool HealRotation::ClearTargetPool() return m_target_pool.empty(); } +bool HealRotation::SetHOTTarget(Mob* hot_target) +{ + if (!hot_target || !IsTargetInPool(hot_target)) + return false; + + m_hot_target = hot_target; + m_hot_active = true; + + return true; +} + +bool HealRotation::ClearHOTTarget() +{ + m_hot_target = nullptr; + m_hot_active = false; + + return true; +} + bool HealRotation::Start() { m_is_active = false; - if (m_member_pool.empty() || m_target_pool.empty()) + if (m_member_pool.empty() || m_target_pool.empty()) { + validate_hot(); return false; + } m_cycle_pool = m_member_pool; m_is_active = true; @@ -207,7 +238,7 @@ bool HealRotation::Stop() Bot* HealRotation::CastingMember() { - if (!m_is_active) + if (!m_is_active && !m_hot_active) return nullptr; if (m_cycle_pool.empty()) { @@ -222,6 +253,9 @@ Bot* HealRotation::CastingMember() bool HealRotation::PokeCastingTarget() { + if (m_hot_target && m_hot_active) + return true; + if (!m_is_active) return false; @@ -248,6 +282,9 @@ bool HealRotation::PokeCastingTarget() Mob* HealRotation::CastingTarget() { + if (m_hot_target && m_hot_active) + return m_hot_target; + if (!m_is_active) return nullptr; if (!m_active_heal_target) @@ -304,6 +341,16 @@ bool HealRotation::IsTargetInPool(Mob* hr_target) return false; } +bool HealRotation::IsHOTTarget(Mob* hot_target) +{ + if (!hot_target) + return false; + if (m_hot_target != hot_target) + return false; + + return true; +} + void HealRotation::SetMemberIsCasting(Bot* hr_member, bool flag) { if (!hr_member) @@ -444,7 +491,7 @@ bool HealRotation::SetArmorTypeSafeHPRatio(uint8 armor_type, float hp_ratio) { if (armor_type >= ARMOR_TYPE_COUNT) return false; - if (hp_ratio < CRITICAL_HP_RATIO_BASE || hp_ratio > SAFE_HP_RATIO_BASE) + if (hp_ratio < CRITICAL_HP_RATIO_ABS || hp_ratio > SAFE_HP_RATIO_ABS) return false; if (hp_ratio < m_critical_hp_ratio[armor_type]) return false; @@ -458,7 +505,7 @@ bool HealRotation::SetArmorTypeCriticalHPRatio(uint8 armor_type, float hp_ratio) { if (armor_type >= ARMOR_TYPE_COUNT) return false; - if (hp_ratio < CRITICAL_HP_RATIO_BASE || hp_ratio > SAFE_HP_RATIO_BASE) + if (hp_ratio < CRITICAL_HP_RATIO_ABS || hp_ratio > SAFE_HP_RATIO_ABS) return false; if (hp_ratio > m_safe_hp_ratio[armor_type]) return false; @@ -863,6 +910,19 @@ void HealRotation::bias_targets() #endif } +void HealRotation::validate_hot() +{ + if (!m_hot_target) { + m_hot_active = false; + return; + } + + if (!IsTargetInPool(m_hot_target)) { + m_hot_target = nullptr; + m_hot_active = false; + } +} + bool IsHealRotationMemberClass(uint8 class_id) { switch (class_id) { diff --git a/zone/heal_rotation.h b/zone/heal_rotation.h index 4b7426623..19a3f8311 100644 --- a/zone/heal_rotation.h +++ b/zone/heal_rotation.h @@ -35,8 +35,11 @@ #define HEALING_STATS_RESET_INTERVAL 60000 #define HEALING_STATS_RESET_INTERVAL_S 60 +#define SAFE_HP_RATIO_ABS 100.0f #define SAFE_HP_RATIO_BASE 95.0f -#define CRITICAL_HP_RATIO_BASE 10.0f + +#define CRITICAL_HP_RATIO_ABS 0.0f +#define CRITICAL_HP_RATIO_BASE 30.0f struct HealingStats { @@ -49,7 +52,7 @@ class HealRotation { public: HealRotation(Bot* hr_creator, uint32 interval_ms = CASTING_CYCLE_DEFAULT_INTERVAL, bool fast_heals = false, bool adaptive_targeting = false, bool casting_override = false); - HealRotation(HealRotation* allocator_shunt) {}; + HealRotation(HealRotation* allocator_shunt) {}; // use should be limited to the shared_ptr memory allocation call void SetIntervalMS(uint32 interval_ms); void SetIntervalS(uint32 interval_s); @@ -72,10 +75,15 @@ public: bool ClearMemberPool(); bool ClearTargetPool(); + Mob* HOTTarget() { return m_hot_target; } + bool SetHOTTarget(Mob* hot_target); + bool ClearHOTTarget(); + bool Start(); bool Stop(); bool IsActive() { return m_is_active; } + bool IsHOTActive() { return m_hot_active; } bool CastingReady() { return (Timer::GetCurrentTime() >= m_next_cast_time_ms); } Bot* CastingMember(); bool PokeCastingTarget(); @@ -88,6 +96,7 @@ public: bool IsMemberInPool(Bot* hr_member); bool IsTargetInPool(Mob* hr_target); + bool IsHOTTarget(Mob* hot_target); void SetMemberIsCasting(Bot* hr_member, bool flag = true); bool MemberIsCasting(Bot* hr_member); @@ -114,6 +123,7 @@ private: void cycle_refresh(); bool healable_target(bool use_class_at = true, bool critical_only = false); void bias_targets(); + void validate_hot(); uint32 m_creation_time_ms; uint32 m_last_heal_time_ms; @@ -131,6 +141,9 @@ private: bool m_consumed; + Mob* m_hot_target; + bool m_hot_active; + std::list m_member_pool; std::list m_cycle_pool; std::list m_target_pool;